用FFMPeg库实现一个视频播放器,涉及到以下环节:
1、图片格式变换
由于解码出来的图片不一定是需要的格式,由于在界面上显示一般是用位图形式,可能也有第三方的显示库如SDL,不需要转化直接显示各种格式的位图。
图片格式转换,可以用滤镜。本文直接调用sws* 函数进行转换。
初始化SwsContext:
g_pSwsContext = sws_getContext(g_decoderVideoCodecContext->width, g_decoderVideoCodecContext->height,
g_decoderVideoCodecContext->pix_fmt,
g_nWindowWidth, g_nWindowHeight,
AV_PIX_FMT_RGBA,
SWS_BICUBIC, NULL, NULL, NULL
);
int len = av_image_get_buffer_size(AV_PIX_FMT_RGBA, g_nWindowWidth, g_nWindowHeight, 1);
g_rgb_buffer = (uint8_t *)av_malloc(len);
av_image_fill_arrays(g_ConvertedFrame.data,
g_ConvertedFrame.linesize,
g_rgb_buffer,
AV_PIX_FMT_RGBA,
g_nWindowWidth,
g_nWindowHeight, 1);
解码后的转换:
int ret = sws_scale(g_pSwsContext,
(const uint8_t* const*)g_pSrcVideoFrame->data,
g_pSrcVideoFrame->linesize,
0,
g_decoderVideoCodecContext->height,
g_ConvertedFrame.data,
g_ConvertedFrame.linesize);
memcpy(image, g_rgb_buffer, g_ConvertedFrame.linesize[0]*g_nWindowHeight);
2、音频格式变换
由于解码出来的音频格式不一定是需要的格式,也需要转化。也可以用滤镜转换,本文也直接调用Swr类的库函数转换了指定的格式:
如果解码出来的不是AV_SAMPLE_FMT_S16,则调用swr_alloc()和swr_init()初始化一个转换用的SwrContext对象。
if (fmt != AV_SAMPLE_FMT_S16)
{
if (g_pSwrContext)
{
swr_free(&g_pSwrContext);
g_pSwrContext = nullptr;
}
int channel_layout = g_inputFormatContext->streams[g_nAudioIndex]->codec->channel_layout;
int sample_rate = g_inputFormatContext->streams[g_nAudioIndex]->codec->sample_rate;
g_pSwrContext = swr_alloc();
av_opt_set_int(g_pSwrContext, "in_channel_layout", channel_layout, 0);
av_opt_set_int(g_pSwrContext, "out_channel_layout", channel_layout, 0);
av_opt_set_int(g_pSwrContext, "in_sample_rate", sample_rate, 0);
av_opt_set_int(g_pSwrContext, "out_sample_rate", sample_rate, 0);
av_opt_set_sample_fmt(g_pSwrContext, "in_sample_fmt", fmt, 0);
av_opt_set_sample_fmt(g_pSwrContext, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
swr_init(g_pSwrContext);
}
解码后的格式转换:
if (g_pSwrContext)
{
int srcNbSamples = g_pSrcAudioFrame->nb_samples;
int srcRate = g_inputFormatContext->streams[g_nAudioIndex]->codec->sample_rate;
int dstRate = srcRate;
int dstNbSamples = av_rescale_rnd(srcNbSamples, dstRate, srcRate, AV_ROUND_UP);
AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
int64_t channel_layout = g_inputFormatContext->streams[g_nAudioIndex]->codec->channel_layout;
int dstNbChannels = av_get_channel_layout_nb_channels(channel_layout);
dstNbChannels = dstNbChannels > 0 ? dstNbChannels : 1;
int ret = av_samples_alloc_array_and_samples(&dst_data, &dstLinesize, dstNbChannels, dstNbSamples, dst_sample_fmt, 0);
ret = swr_convert(g_pSwrContext, dst_data, dstNbSamples, (const uint8_t **)g_pSrcAudioFrame->data, srcNbSamples);
memcpy(g_audio_data_buffer + nAudioBufferIndex, *dst_data, dstLinesize);
nAudioBufferIndex += dstLinesize;
av_freep((void*)&dst_data[0]);
audio = 1;
}
3、播放时音视频同步
本例中以音频时间去同步视频,由于人对音频更敏感,视频不太敏感。
4、工程源码下载:
本例在Debug – x86下编译成功
附带开发环境,本例仅是学习探讨FFPMEG库函数,其中还有不太完善地方需要改进。