FFMPEG + SDL音頻播放分析
睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接
目錄 [hide]
抽象流程:
設置SDL的音頻參數 —-> 打開聲音設備,播放靜音 —-> ffmpeg讀取音頻流中數據放入隊列 —-> SDL調用用戶設置的函數來獲取音頻數據 —-> 播放音頻
SDL內部維護了一個buffer來存放解碼后的數據,這個buffer中的數據來源是我們注冊的回調函數(audio_callback),audio_callback調用audio_decode_frame來做具體的音頻解碼工作,需要引起注意的是:從流中讀取出的一個音頻包(avpacket)可能含有多個音頻楨(avframe),所以需要多次調用avcodec_decode_audio4來完成整個包的解碼,解碼出來的數據存放在我們自己的緩沖中(audio_buf2)。SDL每一次回調都會引起數據從audio_buf2拷貝到SDL內部緩沖區,當audio_buf2中的數據大于SDL的緩沖區大小時,需要分多次拷貝。
關鍵實現:
main()函數
1int main(int argc, char **argv){2 SDL_Event event; //SDL事件變量3 VideoState *is; // 紀錄視頻及解碼器等信息的大結構體4 is = (VideoState*) av_mallocz(sizeof(VideoState));5 if(argc < 2){6 fprintf(stderr, "Usage: play <file>\n");7 exit(1);8 }9 av_register_all(); //注冊所有ffmpeg的解碼器10 /* 初始化SDL,這里只實用了AUDIO,如果有視頻,好需要SDL_INIT_VIDEO等等 */11 if(SDL_Init(SDL_INIT_AUDIO)){12 fprintf(stderr, "Count not initialize SDL - %s\n", SDL_GetError());13 exit(1);14 }15 is_strlcpy(is->filename, argv[1], sizeof(is->filename));16 /* 創建一個SDL線程來做視頻解碼工作,主線程進入SDL事件循環 */17 is->parse_tid = SDL_CreateThread(decode_thread, is);18 if(!is->parse_tid){19 SDL_WaitEvent(&event);20 switch(event.type){21 case FF_QUIT_EVENT:22 case SDL_QUIT:23 is->quit = 1;24 SDL_Quit();25 exit(0);26 break;27 default:28 break;29 }30 }31 return 0;32}decode_thread()讀取文件信息和音頻包
1static int decode_thread(void *arg){2 VideoState *is = (VideoState*)arg;3 AVFormatContext *ic = NULL;4 AVPacket pkt1, *packet = &pkt1;5 int ret, i, audio_index = -1;67 is->audioStream = -1;8 global_video_state = is; 9 /* 使用ffmpeg打開視頻,解碼器等 常規工作 */10 if(avFormat_open_input(&ic, is->filename, NULL, NULL) != 0) {11 fprintf(stderr, "open file error: %s\n", is->filename);12 return -1;13 }14 is->ic = ic;15 if(avformat_find_stream_info(ic, NULL) < 0){16 fprintf(stderr, "find stream info error\n");17 return -1;18 }19 av_dump_format(ic, 0, is->filename, 0);20 for(i = 0; i < ic->nb_streams; i++){21 if(ic->streams[i])->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index == -1){22 audio_index = i;23 break;24 }25 }26 if(audio_index >= 0) {27 /* 所有設置SDL音頻流信息的步驟都在這個函數里完成 */28 stream_component_open(is, audio_index);29 }30 if(is->audioStream < 0){31 fprintf(stderr, "could not open codecs for file: %s\n", is->filename);32 goto fail;33 }34 /* 讀包的主循環, av_read_frame不停的從文件中讀取數據包(這里只取音頻包)*/35 for(;;){36 if(is->quit) break;37 /* 這里audioq.size是指隊列中的所有數據包帶的音頻數據的總量,并不是包的數量 */38 if(is->audioq.size > MAX_AUDIO_SIZE){39 SDL_Delay(10); // 毫秒40 continue;41 }42 ret = av_read_frame(is->ic, packet);43 if(ret < 0){44 if(ret == AVERROR_EOF || url_feof(is->ic->pb)) break;45 if(is->ic->pb && is->ic->pb->error) break;46 contiue; 47 } 48 if(packet->stream_index == is->audioStream){49 packet_queue_put(&is->audioq, packet);50 } else{51 av_free_packet(packet);52 }53 }54 while(!is->quit) SDL_Delay(100);55fail: {56 SDL_Event event;57 event.type = FF_QUIT_EVENT;58 event.user.data1 = is;59 SDL_PushEvent(&event);60 }61 return 0;62}stream_component_open():設置音頻參數和打開設備
1int stream_component_open(videoState *is, int stream_index){2 AVFormatContext *ic = is->ic;3 AVCodecContext *codecCtx;4 AVCodec *codec;5 /* 在用SDL_OpenAudio()打開音頻設備的時候需要這兩個參數*/6 /* wanted_spec是我們期望設置的屬性,spec是系統最終接受的參數 */7 /* 我們需要檢查系統接受的參數是否正確 */8 SDL_AudioSpec wanted_spec, spec;9 int64_t wanted_channel_layout = 0; // 聲道布局(SDL中的具體定義見“FFMPEG結構體”部分) 10 int wanted_nb_channels; // 聲道數11 /* SDL支持的聲道數為 1, 2, 4, 6 */12 /* 后面我們會使用這個數組來糾正不支持的聲道數目 */13 const int next_nb_channels[] = { 0, 0, 1, 6, 2, 6, 4, 6 }; 1415 if(stream_index < 0 || stream_index >= ic->nb_streams) return -1;16 codecCtx = ic->streams[stream_index]->codec;17 wanted_nb_channels = codecCtx->channels;18 if(!wanted_channel_layout || wanted_nb_channels != av_get_channel_layout_nb_channels(wanted_channel_layout)) {19 wanted_channel_layout = av_get_default_channel_lauout(wanted_channel_nb_channels);20 wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;21 }22 wanted_spec.channels = av_get_channels_layout_nb_channels(wanted_channel_layout);23 wanted_spec.freq = codecCtx->sample_rate;24 if(wanted_spec.freq <= 0 || wanted_spec.channels <=0){25 fprintf(stderr, "Invaild sample rate or channel count!\n");26 return -1;27 }28 wanted_spec.format = AUDIO_S16SYS; // 具體含義請查看“SDL宏定義”部分29 wanted_spec.silence = 0; // 0指示靜音30 wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE; // 自定義SDL緩沖區大小31 wanted_spec.callback = audio_callback; // 音頻解碼的關鍵回調函數32 wanted_spec.userdata = is; // 傳給上面回調函數的外帶數據3334 /* 打開音頻設備,這里使用一個while來循環嘗試打開不同的聲道數(由上面 */35 /* next_nb_channels數組指定)直到成功打開,或者全部失敗 */36 while(SDL_OpenAudio(&wanted_spec, &spec) < 0){37 fprintf(stderr, "SDL_OpenAudio(%d channels): %s\n", wanted_spec.channels, SDL_GetError());38 wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)]; // FFMIN()由ffmpeg定義的宏,返回較小的數39 if(!wanted_spec.channels){40 fprintf(stderr, "No more channel to try\n");41 return -1;42 }43 wanted_channel_layout = av_get_default_channel_layout(wanted_spec.channels);44 }45 /* 檢查實際使用的配置(保存在spec,由SDL_OpenAudio()填充) */46 if(spec.format != AUDIO_S16SYS){47 fprintf(stderr, "SDL advised audio format %d is not supported\n", spec.format);48 return -1;49 }50 if(spec.channels != wanted_spec.channels) {51 wanted_channel_layout = av_get_default_channel_layout(spec.channels);52 if(!wanted_channel_layout){53 fprintf(stderr, "SDL advised channel count %d is not support\n", spec.channels);54 return -1;55 }56 }57 /* 把設置好的參數保存到大結構中 */58 is->audio_src_fmt = is->audio_tgt_fmt = AV_SAMPLE_FMT_S16;59 is->audio_src_freq = is->audio_tgt_freq = spec.freq;60 is->audio_src_channel_layout = is->audio_tgt_layout = wanted_channel_layout;61 is->audio_src_channels = is->audio_tat_channels = spec.channels;6263 codec = avcodec_find_decoder(codecCtx>codec_id);64 if(!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)){65 fprintf(stderr, "Unsupported codec!\n");66 return -1;67 }68 ic->streams[stream_index]->discard = AVDISCARD_DEFAULT; //具體含義請查看“FFMPEG宏定義”部分69 is->audioStream = stream_index;70 is->audio_st = ic->streams[stream_index];71 is->audio_buf_size = 0;72 is->audio_buf_index = 0;73 memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));74 packet_queue_init(&is->audioq);75 SDL_PauseAudio(0); // 開始播放靜音76}audio_callback(): 回調函數,向SDL緩沖區填充數據
1void audio_callback(void *userdata, Uint8 *stream, int len){2 VideoState *is = (VideoState*)userdata;3 int len1, audio_data_size;45 /* len是由SDL傳入的SDL緩沖區的大小,如果這個緩沖未滿,我們就一直往里填充數據 */6 while(len > 0){7 /* audio_buf_index 和 audio_buf_size 標示我們自己用來放置解碼出來的數據的緩沖區,*/8 /* 這些數據待copy到SDL緩沖區, 當audio_buf_index >= audio_buf_size的時候意味著我*/9 /* 們的緩沖為空,沒有數據可供copy,這時候需要調用audio_decode_frame來解碼出更10 /* 多的楨數據 */11 if(is->audio_buf_index >= is->audio_buf_size){12 audio_data_size = audio_decode_frame(is);13 /* audio_data_size < 0 標示沒能解碼出數據,我們默認播放靜音 */14 is(audio_data_size < 0){15 is->audio_buf_size = 1024;16 /* 清零,靜音 */17 memset(is->audio_buf, 0, is->audio_buf_size);18 } else{19 is->audio_buf_size = audio_data_size;20 }21 is->audio_buf_index = 0;22 }23 /* 查看stream可用空間,決定一次copy多少數據,剩下的下次繼續copy */24 len1 = is->audio_buf_size - is->audio_buf_index;25 if(len1 > len) len1 = len;2627 memcpy(stream, (uint8_t*)is->audio_buf + is->audio_buf_index, len1);28 len -= len1;29 stream += len1;30 is->audio_buf_index += len1;31 }32}audio_decode_frame():解碼音頻
1int audio_decode_frame(VideoState *is){2 int len1, len2, decoded_data_size;3 AVPacket *pkt = &is->audio_pkt;4 int got_frame = 0;5 int64_t dec_channel_layout;6 int wanted_nb_samples, resampled_data_size;78 for(;;){9 while(is->audio_pkt_size > 0){10 if(!is->audio_frame){11 if(!(is->audio_frame = avacodec_alloc_frame())){12 return AVERROR(ENOMEM);13 }14 } else15 avcodec_get_frame_defaults(is->audio_frame);1617 len1 = avcodec_decode_audio4(is->audio_st_codec, is->audio_frame, got_frame, pkt);18 /* 解碼錯誤,跳過整個包 */19 if(len1 < 0){20 is->audio_pkt_size = 0;21 break;22 }23 is->audio_pkt_data += len1;24 is->audio_pkt_size -= len1;25 if(!got_frame) continue;26 /* 計算解碼出來的楨需要的緩沖大小 */27 decoded_data_size = av_samples_get_buffer_size(NULL,28 is->audio_frame_channels,29 is->audio_frame_nb_samples,30 is->audio_frame_format, 1);31 dec_channel_layout = (is->audio_frame->channel_layout && is->audio_frame->channels32 == av_get_channel_layout_nb_channels(is->audio_frame->channel_layout))33 ? is->audio_frame->channel_layout : av_get_default_channel_layout(is->audio_frame->channels); 34 wanted_nb_samples = is->audio_frame->nb_samples;35 if (is->audio_frame->format != is->audio_src_fmt || 36 dec_channel_layout != is->audio_src_channel_layout ||37 is->audio_frame->sample_rate != is->audio_src_freq || 38 (wanted_nb_samples != is->audio_frame->nb_samples && !is->swr_ctx)) {39 if (is->swr_ctx) swr_free(&is->swr_ctx);40 is->swr_ctx = swr_alloc_set_opts(NULL,41 is->audio_tgt_channel_layout,42 is->audio_tgt_fmt,43 is->audio_tgt_freq,44 dec_channel_layout,45 is->audio_frame->format,46 is->audio_frame->sample_rate,47 0, NULL);48 if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {49 fprintf(stderr, "swr_init() failed\n");50 break;51 }52 is->audio_src_channel_layout = dec_channel_layout;53 is->audio_src_channels = is->audio_st->codec->channels;54 is->audio_src_freq = is->audio_st->codec->sample_rate;55 is->audio_src_fmt = is->audio_st->codec->sample_fmt;56 }57 /* 這里我們可以對采樣數進行調整,增加或者減少,一般可以用來做聲畫同步 */58 if (is->swr_ctx) {59 const uint8_t **in = (const uint8_t **)is->audio_frame->extended_data;60 uint8_t *out[] = { is->audio_buf2 };61 if (wanted_nb_samples != is->audio_frame->nb_samples) {62 if(swr_set_compensation(is->swr_ctx, 63 (wanted_nb_samples - is->audio_frame->nb_samples)*is->audio_tgt_freq/is->audio_frame->sample_rate,64 wanted_nb_samples * is->audio_tgt_freq/is->audio_frame->sample_rate) < 0) {65 fprintf(stderr, "swr_set_compensation() failed\n");66 break;67 }68 }69 len2 = swr_convert(is->swr_ctx, out, 70 sizeof(is->audio_buf2)/is->audio_tgt_channels/av_get_bytes_per_sample(is->audio_tgt_fmt), 71 in, is->audio_frame->nb_samples);72 if (len2 < 0) {73 fprintf(stderr, "swr_convert() failed\n");74 break;75 }76 if(len2 == sizeof(is->audio_buf2)/is->audio_tgt_channels/av_get_bytes_per_sample(is->audio_tgt_fmt)) {77 fprintf(stderr, "warning: audio buffer is probably too small\n");78 swr_init(is->swr_ctx);79 }80 is->audio_buf = is->audio_buf2;81 resampled_data_size = len2*is->audio_tgt_channels*av_get_bytes_per_sample(is->audio_tgt_fmt);82 } else {83 resampled_data_size = decoded_data_size;84 is->audio_buf = is->audio_frame->data[0];85 }86 /* 返回得到的數據 */87 return resampled_data_size;88 }89 if (pkt->data) av_free_packet(pkt);90 memset(pkt, 0, sizeof(*pkt));91 if (is->quit) return -1;92 if (packet_queue_get(&is->audioq, pkt, 1) < 0) return -1;93 is->audio_pkt_data = pkt->data;94 is->audio_pkt_size = pkt->size;9596 }97}FFMPEG結構體
channel_layout_map
1static const struct {2const char *name;3int nb_channels;4uint64_t layout;5} channel_layout_map[] = {6{ "mono", 1, AV_CH_LAYOUT_MONO },7{ "stereo", 2, AV_CH_LAYOUT_STEREO },8{ "2.1", 3, AV_CH_LAYOUT_2POINT1 },9{ "3.0", 3, AV_CH_LAYOUT_SURROUND },10{ "3.0(back)", 3, AV_CH_LAYOUT_2_1 },11{ "4.0", 4, AV_CH_LAYOUT_4POINT0 },12{ "quad", 4, AV_CH_LAYOUT_QUAD },13{ "quad(side)", 4, AV_CH_LAYOUT_2_2 },14{ "3.1", 4, AV_CH_LAYOUT_3POINT1 },15{ "5.0", 5, AV_CH_LAYOUT_5POINT0_BACK },16{ "5.0(side)", 5, AV_CH_LAYOUT_5POINT0 },17{ "4.1", 5, AV_CH_LAYOUT_4POINT1 },18{ "5.1", 6, AV_CH_LAYOUT_5POINT1_BACK },19{ "5.1(side)", 6, AV_CH_LAYOUT_5POINT1 },20{ "6.0", 6, AV_CH_LAYOUT_6POINT0 },21{ "6.0(front)", 6, AV_CH_LAYOUT_6POINT0_FRONT },22{ "hexagonal", 6, AV_CH_LAYOUT_HEXAGONAL },23{ "6.1", 7, AV_CH_LAYOUT_6POINT1 },24{ "6.1", 7, AV_CH_LAYOUT_6POINT1_BACK },25{ "6.1(front)", 7, AV_CH_LAYOUT_6POINT1_FRONT },26{ "7.0", 7, AV_CH_LAYOUT_7POINT0 },27{ "7.0(front)", 7, AV_CH_LAYOUT_7POINT0_FRONT },28{ "7.1", 8, AV_CH_LAYOUT_7POINT1 },29{ "7.1(wide)", 8, AV_CH_LAYOUT_7POINT1_WIDE },30{ "octagonal", 8, AV_CH_LAYOUT_OCTAGONAL },31{ "downmix", 2, AV_CH_LAYOUT_STEREO_DOWNMIX, },32};FFMPEG宏定義
Audio channel convenience macros
1#define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER)2 #define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)3 #define AV_CH_LAYOUT_2POINT1 (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)4 #define AV_CH_LAYOUT_2_1 (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)5 #define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)6 #define AV_CH_LAYOUT_3POINT1 (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)7 #define AV_CH_LAYOUT_4POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)8 #define AV_CH_LAYOUT_4POINT1 (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)9 #define AV_CH_LAYOUT_2_2 (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)10 #define AV_CH_LAYOUT_QUAD (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)11 #define AV_CH_LAYOUT_5POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)12 #define AV_CH_LAYOUT_5POINT1 (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)13 #define AV_CH_LAYOUT_5POINT0_BACK (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)14 #define AV_CH_LAYOUT_5POINT1_BACK (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)15 #define AV_CH_LAYOUT_6POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)16 #define AV_CH_LAYOUT_6POINT0_FRONT (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)17 #define AV_CH_LAYOUT_HEXAGONAL (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)18 #define AV_CH_LAYOUT_6POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)19 #define AV_CH_LAYOUT_6POINT1_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)20 #define AV_CH_LAYOUT_6POINT1_FRONT (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)21 #define AV_CH_LAYOUT_7POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)22 #define AV_CH_LAYOUT_7POINT0_FRONT (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)23 #define AV_CH_LAYOUT_7POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)24#define AV_CH_LAYOUT_7POINT1_WIDE (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)25#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)26#define AV_CH_LAYOUT_OCTAGONAL (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)27#define AV_CH_LAYOUT_STEREO_DOWNMIX (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)SDL宏定義
SDL_AudioSpec format
查看源代碼 打印幫助1AUDIO_U8 Unsigned 8-bit samples2AUDIO_S8 Signed 8-bit samples3AUDIO_U16LSB Unsigned 16-bit samples, in little-endian byte order4AUDIO_S16LSB Signed 16-bit samples, in little-endian byte order5AUDIO_U16MSB Unsigned 16-bit samples, in big-endian byte order6AUDIO_S16MSB Signed 16-bit samples, in big-endian byte order7AUDIO_U16 same as AUDIO_U16LSB (for backwards compatability probably)8AUDIO_S16 same as AUDIO_S16LSB (for backwards compatability probably)9AUDIO_U16SYS Unsigned 16-bit samples, in system byte order10AUDIO_S16SYS Signed 16-bit samples, in system byte ordergit clone https://github.com/lnmcc/musicPlayer.git
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成