最新(2.44)FFmpeg音频播放 ----- 关键点swr_convert

来源:互联网 发布:防水键盘 知乎 编辑:程序博客网 时间:2024/05/02 12:22

ffmpeg 版本:git clone 于 2014-12-02 ,版本接近2.44,在2.44和2.51之间

SDL版本:SDL 1.2(Centos 6.5软件库的相应版本)


       有些旧的ffmpeg播放音频示例中,会存在一些音频可以播放一些不能播放,其中一个我们需要考虑的原因和该注意的地方就是 av_decode_audiole类似函数所获的的AVFrame的格式是否是我们(SDL)所需要的,本例代码用来解决该问题,关键点在于swr_convert函数,代码及注释如下:


</pre><pre name="code" class="cpp">#include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libswscale/swscale.h>#include <libswresample/swresample.h>#include <libavutil/avstring.h>#include <libavutil/pixfmt.h>#include <libavutil/log.h>#include <SDL/SDL.h>#include <SDL/SDL_thread.h>#include <stdio.h>#include <math.h>#define SDL_AUDIO_BUFFER_SIZE 1024 #define MAX_AUDIOQ_SIZE (1 * 1024 * 1024)#define FF_ALLOC_EVENT   (SDL_USEREVENT)#define FF_REFRESH_EVENT (SDL_USEREVENT + 1)#define FF_QUIT_EVENT (SDL_USEREVENT + 2)//该字段存在于旧版本的ffmpeg中,此处粘贴过来使用,勿怪!#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audiotypedef struct PacketQueue {AVPacketList *first_pkt, *last_pkt;int nb_packets;int size;SDL_mutex *mutex;SDL_cond *cond;} PacketQueue;typedef struct VideoState {char filename[1024];AVFormatContext *ic;int videoStream, audioStream;AVStream *audio_st;AVFrame *audio_frame;PacketQueue audioq;unsigned int audio_buf_size;unsigned int audio_buf_index;AVPacket audio_pkt;uint8_t *audio_pkt_data;int audio_pkt_size;uint8_t *audio_buf;uint8_t *audio_buf1;DECLARE_ALIGNED(16,uint8_t,audio_buf2) [AVCODEC_MAX_AUDIO_FRAME_SIZE * 4];enum AVSampleFormat audio_src_fmt;enum AVSampleFormat audio_tgt_fmt;int audio_src_channels;int audio_tgt_channels;int64_t audio_src_channel_layout;int64_t audio_tgt_channel_layout;int audio_src_freq;int audio_tgt_freq;struct SwrContext *swr_ctx;SDL_Thread *parse_tid;int quit;} VideoState;VideoState *global_video_state;void packet_queue_init(PacketQueue *q) {memset(q, 0, sizeof(PacketQueue));q->mutex = SDL_CreateMutex();q->cond = SDL_CreateCond();}int packet_queue_put(PacketQueue *q, AVPacket *pkt) {AVPacketList *pkt1;pkt1 = (AVPacketList *) av_malloc(sizeof(AVPacketList));if (!pkt1) {return -1;}pkt1->pkt = *pkt;pkt1->next = NULL;SDL_LockMutex(q->mutex);if (!q->last_pkt) {q->first_pkt = pkt1;} else {q->last_pkt->next = pkt1;}q->last_pkt = pkt1;q->nb_packets++;q->size += pkt1->pkt.size;SDL_CondSignal(q->cond);SDL_UnlockMutex(q->mutex);return 0;}static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {AVPacketList *pkt1;int ret;SDL_LockMutex(q->mutex);for (;;) {if (global_video_state->quit) {ret = -1;break;}pkt1 = q->first_pkt;if (pkt1) {q->first_pkt = pkt1->next;if (!q->first_pkt) {q->last_pkt = NULL;}q->nb_packets--;q->size -= pkt1->pkt.size;*pkt = pkt1->pkt;av_free(pkt1);ret = 1;break;} else if (!block) {ret = 0;break;} else {SDL_CondWait(q->cond, q->mutex);}}SDL_UnlockMutex(q->mutex);return ret;}int audio_decode_frame(VideoState *is) {int len1, len2, decoded_data_size;AVPacket *pkt = &is->audio_pkt;int got_frame = 0;int64_t dec_channel_layout;int wanted_nb_samples, resampled_data_size;for (;;) {while (is->audio_pkt_size > 0) {if (!is->audio_frame) {if (!(is->audio_frame = av_frame_alloc())) {return AVERROR(ENOMEM);}} elseav_frame_unref(is->audio_frame);/** * 当AVPacket中装得是音频时,有可能一个AVPacket中有多个AVFrame, * 而某些解码器只会解出第一个AVFrame,这种情况我们必须循环解码出后续AVFrame */len1 = avcodec_decode_audio4(is->audio_st->codec, is->audio_frame,&got_frame, pkt);if (len1 < 0) {// error, skip the frameis->audio_pkt_size = 0;break;}is->audio_pkt_data += len1;is->audio_pkt_size -= len1;if (!got_frame)continue;//执行到这里我们得到了一个AVFramedecoded_data_size = av_samples_get_buffer_size(NULL,is->audio_frame->channels, is->audio_frame->nb_samples,is->audio_frame->format, 1);//得到这个AvFrame的声音布局,比如立体声dec_channel_layout =(is->audio_frame->channel_layout&& is->audio_frame->channels== av_get_channel_layout_nb_channels(is->audio_frame->channel_layout)) ?is->audio_frame->channel_layout :av_get_default_channel_layout(is->audio_frame->channels);//这个AVFrame每个声道的采样数wanted_nb_samples = is->audio_frame->nb_samples;/** * 接下来判断我们之前设置SDL时设置的声音格式(AV_SAMPLE_FMT_S16),声道布局, * 采样频率,每个AVFrame的每个声道采样数与 * 得到的该AVFrame分别是否相同,如有任意不同,我们就需要swr_convert该AvFrame, * 然后才能符合之前设置好的SDL的需要,才能播放 */if (is->audio_frame->format != is->audio_src_fmt|| dec_channel_layout != is->audio_src_channel_layout|| is->audio_frame->sample_rate != is->audio_src_freq|| (wanted_nb_samples != is->audio_frame->nb_samples&& !is->swr_ctx)) {if (is->swr_ctx)swr_free(&is->swr_ctx);is->swr_ctx = swr_alloc_set_opts(NULL,is->audio_tgt_channel_layout, is->audio_tgt_fmt,is->audio_tgt_freq, dec_channel_layout,is->audio_frame->format, is->audio_frame->sample_rate,0, NULL);if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {fprintf(stderr, "swr_init() failed\n");break;}is->audio_src_channel_layout = dec_channel_layout;is->audio_src_channels = is->audio_st->codec->channels;is->audio_src_freq = is->audio_st->codec->sample_rate;is->audio_src_fmt = is->audio_st->codec->sample_fmt;}/** * 如果上面if判断失败,就会初始化好swr_ctx,就会如期进行转换 */if (is->swr_ctx) {// const uint8_t *in[] = { is->audio_frame->data[0] };const uint8_t **in =(const uint8_t **) is->audio_frame->extended_data;uint8_t *out[] = { is->audio_buf2 };if (wanted_nb_samples != is->audio_frame->nb_samples) {fprintf(stdout, "swr_set_compensation \n");if (swr_set_compensation(is->swr_ctx,(wanted_nb_samples - is->audio_frame->nb_samples)* is->audio_tgt_freq/ is->audio_frame->sample_rate,wanted_nb_samples * is->audio_tgt_freq/ is->audio_frame->sample_rate) < 0) {fprintf(stderr, "swr_set_compensation() failed\n");break;}}/** * 转换该AVFrame到设置好的SDL需要的样子,有些旧的代码示例最主要就是少了这一部分, * 往往一些音频能播,一些不能播,这就是原因,比如有些源文件音频恰巧是AV_SAMPLE_FMT_S16的。 * swr_convert 返回的是转换后每个声道(channel)的采样数 */len2 = swr_convert(is->swr_ctx, out,sizeof(is->audio_buf2) / is->audio_tgt_channels/ av_get_bytes_per_sample(is->audio_tgt_fmt),in, is->audio_frame->nb_samples);if (len2 < 0) {fprintf(stderr, "swr_convert() failed\n");break;}if (len2== sizeof(is->audio_buf2) / is->audio_tgt_channels/ av_get_bytes_per_sample(is->audio_tgt_fmt)) {fprintf(stderr,"warning: audio buffer is probably too small\n");swr_init(is->swr_ctx);}is->audio_buf = is->audio_buf2;//每声道采样数 x 声道数 x 每个采样字节数resampled_data_size = len2 * is->audio_tgt_channels* av_get_bytes_per_sample(is->audio_tgt_fmt);} else {resampled_data_size = decoded_data_size;is->audio_buf = is->audio_frame->data[0];}// We have data, return it and come back for more laterreturn resampled_data_size;}if (pkt->data)av_free_packet(pkt);memset(pkt, 0, sizeof(*pkt));if (is->quit)return -1;if (packet_queue_get(&is->audioq, pkt, 1) < 0)return -1;is->audio_pkt_data = pkt->data;is->audio_pkt_size = pkt->size;}}void audio_callback(void *userdata, Uint8 *stream, int len) {VideoState *is = (VideoState *) userdata;int len1, audio_data_size;while (len > 0) {if (is->audio_buf_index >= is->audio_buf_size) {audio_data_size = audio_decode_frame(is);if (audio_data_size < 0) {/* silence */is->audio_buf_size = 1024;memset(is->audio_buf, 0, is->audio_buf_size);} else {is->audio_buf_size = audio_data_size;}is->audio_buf_index = 0;}len1 = is->audio_buf_size - is->audio_buf_index;if (len1 > len) {len1 = len;}memcpy(stream, (uint8_t *) is->audio_buf + is->audio_buf_index, len1);len -= len1;stream += len1;is->audio_buf_index += len1;}}/** * 设置SDL播放声音的参数如声音采样格式,声道布局,静音值 */int stream_component_open(VideoState *is, int stream_index) {AVFormatContext *ic = is->ic;AVCodecContext *codecCtx;AVCodec *codec;SDL_AudioSpec wanted_spec, spec;int64_t wanted_channel_layout = 0;int wanted_nb_channels;const int next_nb_channels[] = { 0, 0, 1, 6, 2, 6, 4, 6 };if (stream_index < 0 || stream_index >= ic->nb_streams) {return -1;}codecCtx = ic->streams[stream_index]->codec;wanted_nb_channels = codecCtx->channels;if (!wanted_channel_layout|| wanted_nb_channels!= av_get_channel_layout_nb_channels(wanted_channel_layout)) {wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels);wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;}wanted_spec.channels = av_get_channel_layout_nb_channels(wanted_channel_layout);wanted_spec.freq = codecCtx->sample_rate;if (wanted_spec.freq <= 0 || wanted_spec.channels <= 0) {fprintf(stderr, "Invalid sample rate or channel count!\n");return -1;}wanted_spec.format = AUDIO_S16SYS;wanted_spec.silence = 0;wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;wanted_spec.callback = audio_callback;wanted_spec.userdata = is;while (SDL_OpenAudio(&wanted_spec, &spec) < 0) {fprintf(stderr, "SDL_OpenAudio (%d channels): %s\n",wanted_spec.channels, SDL_GetError());wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)];if (!wanted_spec.channels) {fprintf(stderr,"No more channel combinations to tyu, audio open failed\n");return -1;}wanted_channel_layout = av_get_default_channel_layout(wanted_spec.channels);}if (spec.format != AUDIO_S16SYS) {fprintf(stderr, "SDL advised audio format %d is not supported!\n",spec.format);return -1;}if (spec.channels != wanted_spec.channels) {wanted_channel_layout = av_get_default_channel_layout(spec.channels);if (!wanted_channel_layout) {fprintf(stderr, "SDL advised channel count %d is not supported!\n",spec.channels);return -1;}}fprintf(stderr, "%d: wanted_spec.format = %d\n", __LINE__,wanted_spec.format);fprintf(stderr, "%d: wanted_spec.samples = %d\n", __LINE__,wanted_spec.samples);fprintf(stderr, "%d: wanted_spec.channels = %d\n", __LINE__,wanted_spec.channels);fprintf(stderr, "%d: wanted_spec.freq = %d\n", __LINE__, wanted_spec.freq);fprintf(stderr, "%d: spec.format = %d\n", __LINE__, spec.format);fprintf(stderr, "%d: spec.samples = %d\n", __LINE__, spec.samples);fprintf(stderr, "%d: spec.channels = %d\n", __LINE__, spec.channels);fprintf(stderr, "%d: spec.freq = %d\n", __LINE__, spec.freq);is->audio_src_fmt = is->audio_tgt_fmt = AV_SAMPLE_FMT_S16;is->audio_src_freq = is->audio_tgt_freq = spec.freq;is->audio_src_channel_layout = is->audio_tgt_channel_layout =wanted_channel_layout;is->audio_src_channels = is->audio_tgt_channels = spec.channels;codec = avcodec_find_decoder(codecCtx->codec_id);if (!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)) {fprintf(stderr, "Unsupported codec!\n");return -1;}ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;switch (codecCtx->codec_type) {case AVMEDIA_TYPE_AUDIO:is->audioStream = stream_index;is->audio_st = ic->streams[stream_index];is->audio_buf_size = 0;is->audio_buf_index = 0;memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));packet_queue_init(&is->audioq);SDL_PauseAudio(0);break;default:break;}}/** * demuxing出AVPacket */static int decode_thread(void *arg) {VideoState *is = (VideoState *) arg;AVFormatContext *ic = NULL;AVPacket pkt1, *packet = &pkt1;int ret, i, audio_index = -1;is->audioStream = -1;global_video_state = is;if (avformat_open_input(&ic, is->filename, NULL, NULL) != 0) {return -1;}is->ic = ic;if (avformat_find_stream_info(ic, NULL) < 0) {return -1;}av_dump_format(ic, 0, is->filename, 0);for (i = 0; i < ic->nb_streams; i++) {if (ic->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO&& audio_index < 0) {audio_index = i;break;}}if (audio_index >= 0) {stream_component_open(is, audio_index);}if (is->audioStream < 0) {fprintf(stderr, "%s: could not open codecs\n", is->filename);goto fail;}// main decode loopfor (;;) {if (is->quit)break;if (is->audioq.size > MAX_AUDIOQ_SIZE) {SDL_Delay(10);continue;}ret = av_read_frame(is->ic, packet);if (ret < 0) {if (ret == AVERROR_EOF || url_feof(is->ic->pb)) {break;}if (is->ic->pb && is->ic->pb->error) {break;}continue;}if (packet->stream_index == is->audioStream) {packet_queue_put(&is->audioq, packet);} else {av_free_packet(packet);}}while (!is->quit) {SDL_Delay(100);}fail: {SDL_Event event;event.type = FF_QUIT_EVENT;event.user.data1 = is;SDL_PushEvent(&event);}return 0;}int main(int argc, char *argv[]) {SDL_Event event;VideoState *is;is = (VideoState *) av_mallocz(sizeof(VideoState));if (argc < 2) {fprintf(stderr, "Usage: test <file>\n");exit(1);}av_register_all();if (SDL_Init(SDL_INIT_AUDIO)) {fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());exit(1);}av_strlcpy(is->filename, argv[1], sizeof(is->filename));is->parse_tid = SDL_CreateThread(decode_thread, is);if (!is->parse_tid) {av_free(is);return -1;}for (;;) {SDL_WaitEvent(&event);switch (event.type) {case FF_QUIT_EVENT:case SDL_QUIT:is->quit = 1;SDL_Quit();exit(0);break;default:break;}}return 0;}

FFmpeg版本逐渐更新,代码功能更加丰富和易于使用,掌握音视频基础概念结合ffmpeg就可以方便使用!

1 0