网站首页 > 物联资讯 > 技术分享

FFMPEG + SDL音频播放分析

2016-09-28 00:00:00 广州睿丰德信息科技有限公司 阅读
睿丰德科技 专注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()函数

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.git

RFID管理系统集成商 RFID中间件 条码系统中间层 物联网软件集成