Alsa 读取wave文件,并播放wave 文件
对于一个wave文件,如果需要播放,涉及到几个方面
1.对于wave文件的解析
2.通过解析wave文件,将得到的参数(主要是sampfrequency, bitsperSample,channel)通过alsa api设下去
3.正确找到data的起始点
4.play alsa
1.对于wave文件的解析,需要知道wave文件的格式
注意几点,标准的是44byte的头,但是有些情况下会有additional info, 占据2字节。头信息参见下图,也可以参考wave 文件解析
|
endian |
field name |
Size |
|
| big | ChunkID | 4 | 文件头标识,一般就是" RIFF" 四个字母 |
| little | ChunkSize | 4 | 整个数据文件的大小,不包括上面ID和Size本身 |
| big | Format | 4 | 一般就是" WAVE" 四个字母 |
| big | SubChunk1ID | 4 | 格式说明块,本字段一般就是"fmt " |
| little | SubChunk1Size | 4 | 本数据块的大小,不包括ID和Size字段本身 |
| little | AudioFormat | 2 | 音频的格式说明 |
| little | NumChannels | 2 | 声道数 |
| little | SampleRate | 4 | 采样率 |
| little | ByteRate | 4 | 比特率,每秒所需要的字节数 |
| little | BlockAlign | 2 | 数据块对齐单元 |
| little | BitsPerSample | 2 | 采样时模数转换的分辨率 |
| big | SubChunk2ID | 4 | 真正的声音数据块,本字段一般是"data" |
| little | SubChunk2Size | 4 | 本数据块的大小,不包括ID和Size字段本身 |
| little | Data | N | 音频的采样数据 |
2.设置alsa的参数可以详见代码
3.通过解析wave file可以知道我们data的起始位置
4.通过alsa来play,详见代码
/**
@file TestAlsaPlayWave.cpp
@brief This is a short example to play the audio wave, please define the path in the main func
@par author: jlm
@par pre env: alsa
@todo
*/
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sched.h>
#include <errno.h>
#include <getopt.h>
#include <iostream>
#include <asoundlib.h>
#include <sys/time.h>
#include <math.h>
using namespace std;
/*********Type definition***********************/
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef enum EBitsPerSample
{
BITS_UNKNOWN = ,
BITS_PER_SAMPLE_8 = ,
BITS_PER_SAMPLE_16 = ,
BITS_PER_SAMPLE_32 =
}EBitsPerSample_t;
typedef enum ENumOfChannels
{
NUM_OF_CHANNELS_1 = ,
NUM_OF_CHANNELS_2 =
}ENumOfChannels_t;
#if 0
/** PCM state */
typedef enum _snd_pcm_state {
/** Open */
SND_PCM_STATE_OPEN = ,
/** Setup installed */
SND_PCM_STATE_SETUP,
/** Ready to start */
SND_PCM_STATE_PREPARED,
/** Running */
SND_PCM_STATE_RUNNING,
/** Stopped: underrun (playback) or overrun (capture) detected */
SND_PCM_STATE_XRUN,
/** Draining: running (playback) or stopped (capture) */
SND_PCM_STATE_DRAINING,
/** Paused */
SND_PCM_STATE_PAUSED,
/** Hardware is suspended */
SND_PCM_STATE_SUSPENDED,
/** Hardware is disconnected */
SND_PCM_STATE_DISCONNECTED,
SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED
} snd_pcm_state_t;
#endif
typedef struct ALSA_CONFIGURATION
{
std::string alsaDevice;
std::string friendlyName;
/// Read: Buffer size should be large enough to prevent overrun (read / write buffer full)
unsigned int alsaBufferSize;
/// Chunk size should be smaller to prevent underrun (write buffer empty)
unsigned int alsaPeriodFrame;
unsigned int samplingFrequency;//48kHz
EBitsPerSample bitsPerSample;
ENumOfChannels numOfChannels;
bool block; // false means nonblock
snd_pcm_access_t accessType;
snd_pcm_stream_t streamType; // Playback or capture
unsigned int alsaCapturePeriod; // Length of each capture period
}Alsa_Conf;
typedef struct Wave_Header
{
uint8 ChunkID[];
uint8 ChunkSize[];
uint8 Format[];
uint8 SubChunk1ID[];
uint8 SubChunk1Size[];
uint8 AudioFormat[];
uint8 NumChannels[];
uint8 SampleRate[];
uint8 ByteRate[];
uint8 BlockAlign[];
uint8 BitsPerSample[];
uint8 CombineWaveFileExtra2Bytes[];
uint8 SubChunk2ID[];
uint8 SubChunk2Size[];
}Wave_Header_t;
typedef struct Wave_Header_Size_Info
{
uint8 ChunkID[];
uint8 ChunkSize[];
uint8 Format[];
uint8 SubChunk1ID[];
uint8 SubChunk1Size[];
}Wave_Header_Size_Info_t;
typedef struct Wave_Header_Audio_Info
{
uint8 AudioFormat[];
uint8 NumChannels[];
uint8 SampleRate[];
uint8 ByteRate[];
uint8 BlockAlign[];
uint8 BitsPerSample[];
}Wave_Header_Audio_Info_t;
typedef struct Wave_Header_Additional_Info
{
uint8 AdditionalInfo_2Bytes[]; //this depends on the SubChunk1Size,normal if SubChunk1Size=16 then match the normal wave format, if SubChunk1Size=18 then 2 more additional info bytes
}Wave_Header_Additional_Info_t;
typedef struct Wave_Header_Data_Info
{
uint8 SubChunk2ID[];
uint8 SubChunk2Size[];
}Wave_Header_Data_Info_t;
/*********Global Variable***********************/
snd_pcm_uframes_t g_frames; //just test purpose
/*********Func Declaration***********************/
void TestAlsaDevice(snd_pcm_t** phandler);
bool PrepareAlsaDevice(Alsa_Conf* palsaCfg,snd_pcm_t** phandler);
bool closeAlsaDevice(snd_pcm_t** phandler);
bool ParseWaveFile(const string wavepath,Alsa_Conf* palsaCfg);
uint16 HandleLittleEndian(uint8* arr,int size);
bool PlayWave(FILE** fp,snd_pcm_t** phandler,Alsa_Conf* palsaCfg);
uint16 HandleLittleEndian(uint8* arr,int size)
{
uint16 value=;
;i<size;i++)
{
value=value+(arr[i]<<(*i));
}
return value;
}
#if 0
//this is the return value
ChunkID = "RIFF"
ChunkSize =
Format = "WAVE"
fmt = "fmt "
SubChunk1Size =
AudioFormat =
NumChannels =
SampleRate =
ByteRate =
BlockAlign =
BitsPerSample =
SubChunk2ID = "data"
SubChunk2Size =
#endif
//parse the wave file
bool ParseWaveFile(const string wavepath,Alsa_Conf* palsaCfg,FILE** fp)
{
;
//FILE* fp=NULL;
*fp=fopen(wavepath.c_str(),"rb");
if(*fp==NULL)
{
cout<<"Can parse the wave file-->need check the file name"<<endl;
}
/*********************size info parse begin*************************/
//read size info
Wave_Header_Size_Info_t wav_size_info;
memset(&wav_size_info,,sizeof(Wave_Header_Size_Info_t));
ret=fread(&wav_size_info,,*fp);
)
{
cout<<"read error"<<endl;
return false;
}
string ChunkID="";
;i<;i++)
{
ChunkID+=wav_size_info.ChunkID[i];
}
string riff="RIFF";
!=strcmp(ChunkID.c_str(),riff.c_str()))
{
cout<<"Invalid the fist Chunk id"<<endl;
return false;
}
uint16 ChunkSize=HandleLittleEndian(wav_size_info.ChunkSize,);
cout<<"The ChunSize is "<<ChunkSize<<endl;
string Format="";
;i<;i++)
{
Format+=wav_size_info.Format[i];
}
!=strcmp(Format.c_str(),"WAVE"))
{
cout<<"Invalid the format"<<endl;
return false;
}
string SubChunk1ID="";
;i<;i++)
{
SubChunk1ID+=wav_size_info.SubChunk1ID[i];
}
string fmt="fmt ";
!=strcmp(SubChunk1ID.c_str(),fmt.c_str()))
{
cout<<"Invalid the SubChunk1ID "<<endl;
return false;
}
uint16 SubChunk1Size=HandleLittleEndian(wav_size_info.SubChunk1Size,);
&& SubChunk1Size!=)
{
cout<<"Invalid the SubChunk1Size"<<endl;
return false;
}
/*********************Audio info parse begin*************************/
Wave_Header_Audio_Info_t wav_audio_info;
memset(&wav_audio_info,,sizeof(Wave_Header_Audio_Info_t));
ret=fread(&wav_audio_info,,*fp);
)
{
cout<<"read error"<<endl;
return false;
}
//fseek(fp,sizeof(Wave_Header_Size_Info_t),0);//文件指针偏移3个字节到'2' because fread will shift the pointer
uint16 AudioFormat =HandleLittleEndian(wav_audio_info.AudioFormat,);
uint16 NumChannels =HandleLittleEndian(wav_audio_info.NumChannels,);
uint16 SampleRate =HandleLittleEndian(wav_audio_info.SampleRate,);
uint16 ByteRate =HandleLittleEndian(wav_audio_info.ByteRate,);
uint16 BlockAlign =HandleLittleEndian(wav_audio_info.BlockAlign,);
uint16 BitsPerSample=HandleLittleEndian(wav_audio_info.BitsPerSample,);
palsaCfg->numOfChannels=(ENumOfChannels)NumChannels;
palsaCfg->samplingFrequency=SampleRate;
palsaCfg->bitsPerSample=(EBitsPerSample)BitsPerSample;
/*********************Additional info parse begin if needed*************************/
)
{
Wave_Header_Additional_Info_t wav_additional_info;
memset(&wav_additional_info,,sizeof(Wave_Header_Additional_Info_t));
fread(&wav_additional_info,,*fp);
cout<<"read wav_additional_info"<<endl;
)
{
cout<<"read error"<<endl;
return false;
}
uint16 AdditionalInfo=HandleLittleEndian(wav_additional_info.AdditionalInfo_2Bytes,);
cout<<"read AdditionalInfo value="<<AdditionalInfo<<endl;
}
/*********************Data info parse begin *************************/
Wave_Header_Data_Info_t wave_data_info;
memset(&wave_data_info,,sizeof(Wave_Header_Data_Info_t));
fread(&wave_data_info,,*fp);
)
{
cout<<"read error"<<endl;
return false;
}
string SubChunk2ID="";
;i<;i++)
{
SubChunk2ID+=wave_data_info.SubChunk2ID[i];
}
string fact="fact";
string data="data";
==strcmp(SubChunk2ID.c_str(),fact.c_str()))
{
cout<<"SubChunk2ID fact"<<endl;
}
==strcmp(SubChunk2ID.c_str(),data.c_str()))
{
cout<<"SubChunk2ID data"<<endl;
}
else
{
cout<<"Invalid SubChunk2ID "<<endl;
return false;
}
uint16 SubChunk2Size=HandleLittleEndian(wave_data_info.SubChunk2Size,);
cout<<"End Parse"<<endl;
return true;
}
bool PlayWave(FILE** fp,snd_pcm_t** phandler,Alsa_Conf* palsaCfg)
{
;
bool ret=false;
snd_pcm_uframes_t frames=palsaCfg->alsaPeriodFrame;
; //4bytes
uint16 audio_data_size=frames*bytesPerFrame;//one period 10ms ,1600*10/1000*(2*16/8)=640bytes one period
uint8* buffer=new uint8[audio_data_size];
cout<<"Start play wave"<<endl;
if(*fp==NULL || *phandler==NULL || palsaCfg==NULL)
{
cout<<"End play wave because something is NULL"<<endl;
return false;
}
//fseek(*fp,46,SEEK_SET); //no need to do fseek because already shifted
cout<<"available frame "<<snd_pcm_avail(*phandler)<<"my frames is "<<frames<<endl;
while(true)
{
if(feof(*fp))
{
cout<<"Reach end of the file"<<endl;
break;
}
else
{
if(snd_pcm_avail(*phandler)<frames)
{
continue;
}
else
{
memset(reinterpret_cast<,sizeof(uint8)*audio_data_size);
err=fread(buffer,,*fp);
)
{
cout<<"read error"<<endl;
}
if ( NULL != buffer )
{
err = snd_pcm_writei(*phandler, buffer, frames);
)
{
cout<<"Fail to write the audio data to ALSA. Reason: "<<(snd_strerror(err));
// recover ALSA device
err = snd_pcm_recover(*phandler, err, );
)
{
cout<<"Fail to recover ALSA device. Reason: "<<(snd_strerror(err));
ret = false;
}
else
{
cout<<"ALSA device is recovered from error state"<<endl;
}
}
}
else
{
cout<<"Write buffer is NULL!"<<endl;
}
}
}
usleep(palsaCfg->alsaCapturePeriod / ( * ));
}
delete[] buffer;
buffer=NULL;
return ret;
}
bool PrepareAlsaDevice(Alsa_Conf* palsaCfg,snd_pcm_t** phandler)
{
bool ret=false;
bool success=true;
;
snd_pcm_format_t format;
snd_pcm_hw_params_t *hw_params = NULL;
;
if(palsaCfg!=NULL)
{
// open ALSA device
error=snd_pcm_open(phandler,palsaCfg->alsaDevice.c_str(),palsaCfg->streamType,palsaCfg->block? :SND_PCM_NONBLOCK);
) //0 on success otherwise a negative error code
{
success=false;
cout<<"Open Alsadevice error error code="<<snd_strerror(error)<<endl;
}
if(success)
{
//allocate hardware parameter structur
error=snd_pcm_hw_params_malloc(&hw_params);//alsao can use snd_pcm_hw_params_alloca(&hwparams)
)
{
success=false;
hw_params=NULL;
cout<<"Set hw params error error code="<<snd_strerror(error)<<endl;
}
}
if(success)
{
//Fill params with a full configuration space for a PCM. initialize the hardware parameter
error=snd_pcm_hw_params_any(*phandler,hw_params);
)
{
success=false;
cout<<"Broken configuration for PCM: no configurations available: "<<snd_strerror(error)<<endl;
}
}
if(success)
{
// set the access type
error = snd_pcm_hw_params_set_access(*phandler, hw_params, palsaCfg->accessType);
)
{
cout<<"[SG]Fail to set access type. Reason: "<<snd_strerror(error)<<endl;
success = false;
}
}
if(success)
{
switch (palsaCfg->bitsPerSample)
{
case BITS_PER_SAMPLE_8:
{
format = SND_PCM_FORMAT_U8;
break;
}
case BITS_PER_SAMPLE_16:
{
format = SND_PCM_FORMAT_S16_LE; //indicate this was little endian
break;
}
case BITS_PER_SAMPLE_32:
{
format = SND_PCM_FORMAT_S32_LE;
break;
}
default:
{
format = SND_PCM_FORMAT_S16_LE;
cout<<"Invalid format"<<endl;
success=false;
}
}
if(success)
{
error=snd_pcm_hw_params_set_format(*phandler,hw_params,format);
)
{
cout<<"set format not available for "<<snd_strerror(error)<<endl;
success=false;
}
}
}
if(success)
{
error=snd_pcm_hw_params_set_rate_near(*phandler,hw_params,&palsaCfg->samplingFrequency,);
)
{
cout<<"set rate not available for "<<snd_strerror(error)<<endl;
success=false;
}
}
if(success)
{
error=snd_pcm_hw_params_set_channels(*phandler,hw_params,palsaCfg->numOfChannels);
)
{
cout<<"set_channels not available for "<<snd_strerror(error)<<endl;
success=false;
}
}
if (success)
{
// set period size (period size is also a chunk size for reading from ALSA)
snd_pcm_uframes_t alsaPeriodFrame = static_cast<snd_pcm_uframes_t>(palsaCfg->alsaPeriodFrame); // One frame could be 4 bytes at most
// set period size
error = snd_pcm_hw_params_set_period_size_near(*phandler, hw_params, &alsaPeriodFrame, &dir);
)
{
cout<<"[SG]Fail to set period size. Reason: "<<snd_strerror(error)<<endl;
success = false;
}
}
if (success)
{
// set hardware parameters
error = snd_pcm_hw_params(*phandler, hw_params);
)
{
cout<<"[SG]Fail to set hardware parameter. Reason: "<<snd_strerror(error)<<endl;
success = false;
}
}
if (success)
{
error=snd_pcm_hw_params_get_period_size(hw_params, &g_frames, &dir); //get frame
cout<<"Frame is "<<g_frames<<endl;
// free the memory for hardware parameter structure
snd_pcm_hw_params_free(hw_params);
hw_params = NULL;
// Prepare ALSA device
error = snd_pcm_prepare(*phandler);
)
{
cout<<"Fail to prepare ALSA device. Reason: "<<(snd_strerror(error))<<endl;
success = false;
}
}
if (success)
{
cout<<"ALSA device is ready to use"<<endl;
}
else
{
// fail to prepare ALSA device ==> un-initialize ALSA device
if (hw_params != NULL)
{
snd_pcm_hw_params_free(hw_params);
hw_params = NULL;
}
closeAlsaDevice(phandler);
}
}
return success;
}
bool closeAlsaDevice(snd_pcm_t** phandler)
{
bool ret = true;
snd_pcm_state_t state;
int snd_ret;
if (*phandler != NULL)
{
// drop the pending audio frame if needed
state = snd_pcm_state(*phandler);
cout<<"Alsa handler sate: "<<state<<endl;
if ((SND_PCM_STATE_RUNNING == state) || (SND_PCM_STATE_XRUN == state) || (SND_PCM_STATE_SUSPENDED == state))
{
snd_ret = snd_pcm_drop(*phandler);
)
{
cout<<"Fail to drop ALSA device. Reason: "<<(snd_strerror(snd_ret))<<endl;
}
}
// close ALSA handler
snd_ret = snd_pcm_close(*phandler);
)
{
cout<<"Fail to close ALSA device. Reason: "<<(snd_strerror(snd_ret))<<endl;
ret = false;
}
*phandler = NULL;
cout<<"CLOSE ALSA DEVICE"<<endl;
}
return ret;
}
int main()
{
bool ret=false;
snd_pcm_t* m_phandler=NULL;
Alsa_Conf* m_palsaCfg=new Alsa_Conf();
m_palsaCfg->alsaDevice = string("sd_out_16k");
//m_palsaCfg->samplingFrequency = 16000;
m_palsaCfg->alsaCapturePeriod = ;
//m_palsaCfg->numOfChannels = NUM_OF_CHANNELS_1;
m_palsaCfg->block = true; //block
m_palsaCfg->friendlyName = "AlsaWave";
//m_palsaCfg->bitsPerSample = BITS_PER_SAMPLE_16;
m_palsaCfg->alsaPeriodFrame = m_palsaCfg->samplingFrequency * m_palsaCfg->alsaCapturePeriod / ; // calculate the number of frame in one period
m_palsaCfg->alsaBufferSize = m_palsaCfg->alsaPeriodFrame * ; //means the whole buffer was perdion*8, e.g. 10ms means every 10ms we will get/send the data
m_palsaCfg->accessType = SND_PCM_ACCESS_RW_INTERLEAVED;
m_palsaCfg->streamType = SND_PCM_STREAM_PLAYBACK;
FILE* fp=NULL;
const string wavePath="/mnt/hgfs/0_SharedFolder/0_Local_Test_Folder/01_TestFolder/TestALSA/left_1k_right_400hz.wav";
//parse the wave file
ret=ParseWaveFile(wavePath,m_palsaCfg,&fp);
//update the value
m_palsaCfg->alsaPeriodFrame = m_palsaCfg->samplingFrequency * m_palsaCfg->alsaCapturePeriod / ; // calculate the number of frame in one period
if(ret)
{
//open alsa device
ret=PrepareAlsaDevice(m_palsaCfg,&m_phandler);
}
if(ret)
{
PlayWave(&fp,&m_phandler,m_palsaCfg);
}
closeAlsaDevice(&m_phandler);
if(fp!=NULL)
{
fclose(fp);
fp=NULL;
}
delete m_palsaCfg;
m_palsaCfg=NULL;
;
}
Alsa 读取wave文件,并播放wave 文件的更多相关文章
- 读取SD卡文件夹下的MP3文件和播放MP3文件
首先获取SD卡path路径下的所有的MP3文件,并将文件名和文件大小存入List数组(此代码定义在FileUtils类中): /** * 读取目录中的Mp3文件的名字和大小 */ public Lis ...
- Linux音频编程--使用ALSA库播放wav文件
在UBUNTU系统上使用alsa库完成了对外播放的wav文件的案例. 案例代码: /** *test.c * *注意:这个例子在Ubuntu 12.04.1环境下编译运行成功. * */ #inclu ...
- Opencv 播放mp4文件和读取摄像头图以及可能会发生的一些异常问题解决方法
学习内容 学习Opencv 读取并播放本地视频和打开摄像头图像以及可能会发生的一些异常问题解决方法 代码演示 电脑环境信息: OpenCV版本:4.5.2 ,vs2017 1.视频文件读取与播放 加载 ...
- python 播放 wav 文件
未使用其他库, 只是使用 pywin32 调用系统底层 API 播放 wav 文件. # Our raison d'etre - playing sounds import pywintypes im ...
- 【转】C# 视频监控系列(12):H264播放器——播放录像文件
原文地址:http://www.cnblogs.com/over140/archive/2009/03/23/1419643.html?spm=5176.100239.blogcont51182.16 ...
- VC++中MCI播放音频文件 【转】
MCI播放mp3音频文件例程 源文件中需要包含头文件 Mmsystem.h,在Project->Settings->Link->Object/libray module中加入库 Wi ...
- 如何播放 WAV 文件?
from http://www.vckbase.com/index.php/wv/434 平时,你在多媒体软件的设计中是怎样处理声音文件的呢?使用Windows 提供的API函数 sndPlaySou ...
- ffmpeg和opencv 播放视频文件和显示器
ffmpeg它是基于最新版本,在官网下载http://ffmpeg.zeranoe.com/builds/.编译时VS2010配置相关头文件及库的路径就可以.opencv的搭建參考上一个博客. 首先简 ...
- STM32音乐播放器,文件查找的实现
使用FATFS只是完成了一个基本的文件读写,有时候我们需要扩展一些功能,比如MP3实验,需要上一曲下一曲的切换,扩展的代码如下 //显示目录下所有文件 u8 ShowFileList(u8* dirP ...
- Qt 播放音频文件
Qt播放音频文件的方法有好多中,简单介绍几种 不过一下几种方式都需要在Qt工程文件中添加 QT += multimedia 第一 QMediaPlayer类 可以播放MP3文件,同时使用也 ...
随机推荐
- Atom 编辑器试用
简介 它号称"21世纪可黑客的文本编辑器".GitHub支持并开源,并支持跨平台.和brackets编辑器一样基于浏览器开发,意味着你可以使用less(包含css)来定制编辑器界面 ...
- 山谈c中printf格式修饰符
废话不多说,简单粗暴地上图. (一)初始定义 (二)运行结果 (三)规律总结 对于如下: printf("%7.4d",12); printf("%4.7f", ...
- linux从入门到精通学习-NFS
NFS网络文件系统 功能 nfs[network file system] 网络文件系统 是FreBSD系统支持的一种系统,允许在网络 上与其它人共享使用文件或文件夹 采用C/S模式 端口号 在vim ...
- Android5.0水波纹效果ripple实现
1.如何设置波纹效果 // 波纹有边界 android:background="?android:attr/selectableItemBackground" // 波纹超出边界 ...
- 02 Learning to Answer Yes/No
Perceptron Learning Algorithm 感知器算法, 本质是二元线性分类算法,即用一条线/一个面/一个超平面将1,2维/3维/4维及以上数据集根据标签的不同一分为二. 算法确定后, ...
- Python JavaScript概述
一.如何编写? 1.JavaScript代码存在形式 <!DOCTYPE html> <html> <head> <meta http-equiv=" ...
- 基础SELECT示例掌握
SELECT查询语句 ---进行单条记录.多条记录.单表.多表.子查询-- SELECT [ALL | DISTINCT | DISTINCTROW ] [HIGH_PRIORITY] [MAX_ST ...
- node.js系列:(调试工具)node-inspector调试Node.js应用
如果你在编写Node.js代码,node-inspector是必备之选,比Node.js的内置调试器好出许多.使用起来跟Chrome的javascript调试器很相似. 使用npm安装: $ npm ...
- jquery实现图片上传前本地预览
<!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8" ...
- IDEA第五章----Git常用技能
前几篇已经介绍了idea的环境搭建及基础配置常用模板等,这一章我们介绍下idea中git的一些常用技能,包括提交文件,排除提交文件,合并分支,解决冲突,还原代码等等等. 第一节:Git常用技能 Git ...