FFMPEG + SDL音頻播放分析

目錄 [hide]git

抽象流程:

設置SDL的音頻參數 —-> 打開聲音設備,播放靜音 —-> ffmpeg讀取音頻流中數據放入隊列 —-> SDL調用用戶設置的函數來獲取音頻數據 —-> 播放音頻github

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()函數

1 int 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()讀取文件信息和音頻包

1 static 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;
6  
7     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);
55 fail: {
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():設置音頻參數和打開設備

1 int 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 };
14  
15     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; // 傳給上面回調函數的外帶數據
33  
34     /*  打開音頻設備,這裏使用一個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;
62  
63     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緩衝區填充數據

1 void audio_callback(void *userdata, Uint8 *stream, int len){
2     VideoState *is = (VideoState*)userdata;
3     int len1, audio_data_size;
4  
5     /*   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;
26  
27         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():解碼音頻

1 int 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;
7  
8     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         } else
15           avcodec_get_frame_defaults(is->audio_frame);
16  
17         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->channels
32                    == 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;
95  
96      }
97 }

FFMPEG結構體

channel_layout_map

1 static const struct {
2 const char *name;
3 int nb_channels;
4 uint64_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

1 AUDIO_U8           Unsigned 8-bit samples
2 AUDIO_S8            Signed 8-bit samples
3 AUDIO_U16LSB    Unsigned 16-bit samples, in little-endian byte order
4 AUDIO_S16LSB    Signed 16-bit samples, in little-endian byte order
5 AUDIO_U16MSB    Unsigned 16-bit samples, in big-endian byte order
6 AUDIO_S16MSB    Signed 16-bit samples, in big-endian byte order
7 AUDIO_U16           same as AUDIO_U16LSB (for backwards compatability probably)
8 AUDIO_S16           same as AUDIO_S16LSB (for backwards compatability probably)
9 AUDIO_U16SYS    Unsigned 16-bit samples, in system byte order
10 AUDIO_S16SYS     Signed 16-bit samples, in system byte order

git clone https://github.com/lnmcc/musicPlayer.gitide

相關文章
相關標籤/搜索