AUDIO大致流程:
//初始化输入
avformat_open_input() -- 打开对应音频文件
avformat_find_stream_info() -- 从输入文件中获取到流的相关信息,例如:文件中流的数量
//初始化解码器
avcodec_find_decoder() -- 根据ffmpeg提供的解码器id,找到对应的解码器
avcodec_open2() -- 打开解码器
//初始化输出
avformat_alloc_context() -- 创建输出信息上下文
avcodec_find_encoder() -- 找到编码器
avcodec_alloc_context3() -- 根据编码器,初始化编码上下文
avcodec_open2() -- 打开编码器
//初始化一个FIFO(先进先出)
av_audio_fifo_alloc() -- 初始化一个先进先出的缓存,用了存储解码后的pcm数据
//初始化音频重采用器
swr_alloc_set_opts() -- 设置转化器参数
swr_init() -- 初始化转换器
//开始音频转换
while(finished) {
// 解码
av_read_frame() -- 读取要进行转码的数据
avcodec_decode_audio4() -- 进行解码
av_samples_alloc() -- 创建样本空间
swr_convert() -- 数据重采样
av_audio_fifo_write() -- 将数据存储到fifo缓存中
// 编码
av_audio_fifo_read() -- 从fifo缓存中读取pcm数据
avcodec_encode_audio2() -- 将数据进行编码
}
1、SDL
现在我们要来播放声音。SDL 也为我们准备了输出声音的方法。函数 SDL_OpenAudio()本身就是用
来打开声音设备的。它使用一个叫做 SDL_AudioSpec 结构体作为参数,这个结构体中包含了我们将要输
出的音频的所有信息。
在我们展示如何建立之前,让我们先解释一下电脑是如何处理音频的。数字音频是由一长串的样本流
组成的。每个样本表示声音波形中的一个值。声音按照一个特定 的采样率来进行录制,采样率表示以多
快的速度来播放这段样本流,它的表示方式为每秒多少次采样。例如 22050 和 44100 的采样率就是电台
和 CD 常用的 采样率。此外,大多音频有不只一个通道来表示立体声或者环绕。例如,如果采样是立体
声,那么每次的采样数就为 2 个。当我们从一个电影文件中等到数据的时 候,我们不知道我们将得到多
少个样本,但是 ffmpeg 将不会给我们部分的样本――这意味着它将不会把立体声分割开来。
SDL 播放声音的方式是这样的:你先设置声音的选项:采样率(在 SDL 的结构体中被叫做 freq 的表
示频率 frequency),声音通道数和其它的参 数,然后我们设置一个回调函数和一些用户数据 userdata。
当开始播放音频的时候,SDL 将不断地调用这个回调函数并且要求它来向声音缓冲填入一个特 定的数量
的字节。当我们把这些信息放到 SDL_AudioSpec 结构体中后,我们调用函数 SDL_OpenAudio()就会打开
声音设备并且给我们送 回另外一个 AudioSpec 结构体。这个结构体是我们实际上用到的--因为我们不
能保证得到我们所要求的
2、核心转换函数
int | swr_convert (struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count) |
3、SDL回调
fill_auido()当中将audio_pos叠加在audio预设的数据stream,就是混播音频的关键,其次auido_len确保了有序
示例代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #define __STDC_CONSTANT_MACROS #ifdef _WIN32 //Windows extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswresample/swresample.h" #include "SDL2/SDL.h" }; #else //Linux... #ifdef __cplusplus extern "C" { #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> #include <SDL2/SDL.h> #ifdef __cplusplus }; #endif #endif /* *SDL播放声音的基本流程如下: *(1)创建一个回调函数用于混合音频数据,并放入音频流; *(2)设置音频参数,调用SDL_OpenAudio,打开音频设备; *(3)调用SDL_PauseAudio(0),进行音频回放; *(4)回放结束后,调用SDL_CloseAudio() 关闭音频设备。 */ #define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio //Output PCM #define OUTPUT_PCM 1 //Use SDL #define USE_SDL 1 static Uint8 *audio_chunk; static Uint32 audio_len; static Uint8 *audio_pos; //回调函数用于混合音频数据,并放入音频流 void fill_audio(void *udata,Uint8 *stream,int len){ //printf("------------>audio_len == %d\n",audio_len); SDL_memset(stream,0,len); if(audio_len == 0) return ; len = (len > audio_len ? audio_len : len); //在fill_audio()当中将每个数据叠加在audio预设的数据流stream,就是混播音频的关键。 SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME); audio_pos += len; audio_len -= len; //printf("------------>audio_len == %d\n",audio_len); } int main(int argc,char* argv[]){ //以下参数跟视频播放一样 AVFormatContext *pFormatCtx; int i, audioStream; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVPacket *packet; uint8_t *out_buffer; AVFrame *pFrame; //如下介绍 SDL_AudioSpec wanted_spec; int ret; uint32_t len = 0; int got_picture; int index = 0; int64_t in_channel_layout; struct SwrContext *au_convert_ctx; FILE *pFile = NULL; char url[] = "WavinFlag.aac"; av_register_all(); pFormatCtx = avformat_alloc_context(); if(avformat_open_input(&pFormatCtx,url,NULL,NULL) != 0){ printf("Could not open input stream\n"); return -1; } if(avformat_find_stream_info(pFormatCtx,NULL) < 0){ printf("Could not find stream infomation.\n"); return -1; } av_dump_format(pFormatCtx,0,url,false); audioStream = -1; for(i = 0;i < pFormatCtx->nb_streams;i++){ if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){ audioStream = i; break; } } if(audioStream == -1){ printf("Do not find a audio stream \n"); return -1; } pCodecCtx = pFormatCtx->streams[audioStream]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(NULL == pCodec){ printf("Codec not find.\n"); return -1; } if(avcodec_open2(pCodecCtx,pCodec,NULL)<0){ printf("Could not open codec.\n"); return -1; } #if OUTPUT_PCM pFile = fopen("output.pcm","wb"); #endif packet = (AVPacket*)av_malloc(sizeof(AVPacket)); av_init_packet(packet); //设置Audio参数 uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO; int out_nb_samples = pCodecCtx->frame_size; AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16; int out_sample_rate = 44100; int out_channels = av_get_channel_layout_nb_channels(out_channel_layout); //存放解码后的数据用于写pcm文件 int out_buffer_size = av_samples_get_buffer_size(NULL,out_channels,out_nb_samples,out_sample_fmt,1); out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE*2); //存放解码后数据结构 pFrame = av_frame_alloc(); //SDL #if USE_SDL if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){ printf("SDL Init failed.\n"); return -1; } //SDL_AudioSpec /*freq: DSP 频率 (每秒采样); *format:音频数据格式; *channels:声道数: 1 单声道, 2 立体声; *silence:audio buffer silence value (计算得); *samples:audio buffer size in samples (power of 2); *size:音频缓存区大小(字节数)(计算得); *callback:当音频设备需要更多数据时调用的回调函数; *userdata:传递给 callback 函数的第一个参数 */ wanted_spec.freq = out_sample_rate; wanted_spec.format = AUDIO_S16SYS; wanted_spec.channels = out_channels; wanted_spec.samples = out_nb_samples; wanted_spec.callback = fill_audio; wanted_spec.userdata = pCodecCtx; //打开音频设备 if(SDL_OpenAudio(&wanted_spec,NULL)<0){ printf("can not open audio.\n"); return -1; } printf("Bitrate:\t %3d\n", pFormatCtx->bit_rate); printf("Decoder Name:\t %s\n", pCodecCtx->codec->long_name); printf("Channels:\t %d\n", pCodecCtx->channels); printf("Sample per Second\t %d \n", pCodecCtx->sample_rate); #endif /** * 接下来判断我们之前设置SDL时设置的声音格式(AV_SAMPLE_FMT_S16),声道布局, * 采样频率,每个AVFrame的每个声道采样数与 * 得到的该AVFrame分别是否相同,如有任意不同,我们就需要swr_convert该AvFrame, * 然后才能符合之前设置好的SDL的需要,才能播放 */ //通过av_get_channel_layout_nb_channels()和av_get_default_channel_layout()这些函数可以得到channels和channellayout的转换。 in_channel_layout = av_get_default_channel_layout(pCodecCtx->channels); //初始化音频重采用器 au_convert_ctx = swr_alloc(); au_convert_ctx = swr_alloc_set_opts(au_convert_ctx,out_channel_layout,out_sample_fmt,out_sample_rate, in_channel_layout,pCodecCtx->sample_fmt,pCodecCtx->sample_rate,0,NULL); swr_init(au_convert_ctx); //用此函数来暂停播放,或播放。根据参数来决定,如果参数是非0值就暂停,如果是0值就播放 SDL_PauseAudio(0); while(av_read_frame(pFormatCtx,packet)>=0){ if(audioStream == packet->stream_index){ //使用avcodec_decode_audio4函数来解码音频 ret = avcodec_decode_audio4(pCodecCtx,pFrame,&got_picture,packet); if(0 > ret){ printf("Error in decoding audio frame.\n"); return -1; } if(0 < got_picture){ //数据重采样提供给SDL播放 /*由于ffmpeg最新版本(从2.1开始貌似)使用avcodec_decode_audio4函数来解码音频, *但解码得到的数据类型为float 4bit,而播放器播放的格式一般为S16(signed 16bit) *,就需要对解码得到的数据进行转换,然而,ffmpeg已经帮我们做好了, *只需调用API就可以了,这个函数就是:swr_convert *out :输出缓冲区,只有第一个需要设置的情况下,包装音频 *out_count :每通道可用输出样本的数量 *in:输入缓冲区,只有第一个需要设置的情况下,包装音频,解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM) *in_count :每通道可用输入样本的数量(音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个) */ swr_convert(au_convert_ctx,&out_buffer,MAX_AUDIO_FRAME_SIZE,(const uint8_t**)pFrame->data,pFrame->nb_samples); //printf("index : %5d\t pts : %lld\t packet size : %d\n",index,packet->pts,packet->size); #if OUTPUT_PCM fwrite(out_buffer,1,out_buffer_size,pFile); #endif index ++; } #if USE_SDL while(audio_len > 0){ //注意audio_len的用法 //保证fill_audio回调有序的播放每一帧数据 //上一帧SDL_MixAudio之后auido_len = 0,跳出塞下一帧 SDL_Delay(1); } //每次循环的时候都更新数据的buffer、pos等信息, //在每次SDL_PauseAudio回调的时候都能够播放下一时刻的数据 //(注意 这里必须把每一个PCM文件的数据都进行更新)。 audio_chunk = (Uint8 *)out_buffer; audio_len = out_buffer_size; audio_pos = audio_chunk; #endif } av_free_packet(packet); } swr_free(&au_convert_ctx); #if USE_SDL SDL_CloseAudio(); SDL_Quit(); #endif #if OUTPUT_PCM fclose(pFile); #endif av_free(out_buffer); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; }