第4课 FFmpeg读取本地mp4文件并显示

在上节课,我们使用FFmpeg实现了一个最简单的rtmp播放器,它看起来工作正常。这节课,我们尝试让它来播放本地的mp4文件试试。

1.压缩备份上节课工程文件夹为demo3.rar,并修改工程文件夹demo3为demo4,重要的事情再说一遍:及时备份源文件并在原基础上继续迭代开发是一种好习惯。

将原rtmp地址修改为本地mp4地址:

cpp 复制代码
const char *inFileName = "d:\\mp4\\dtz.mp4";	

调试运行,会发现视频显示一卡一卡,音频也断断续续的。这是什么原因呢?

2.经过很长时间的研究学习,我才发现:原来是流的时间基与当前ffmpeg的时间基不一致造成的。根据以上信息,将延时函数修改如下:

cpp 复制代码
//延时以使当前视频记录的播放时间与实际时间同步
if (normalPkt.stream_index == videoIndex)
{
	AVRational videoTimeBase = inFormatCtx->streams[videoIndex]->time_base;
	AVRational currentTimeBase = { 1, AV_TIME_BASE };
	//计算视频播放时间
	int64_t videoTime = av_rescale_q(normalPkt.dts, videoTimeBase, currentTimeBase);
	//计算实际视频的播放时间
	int64_t currentTime = av_gettime() - startTime;
	if (videoTime > currentTime) {
av_usleep((unsigned int)(videoTime - currentTime));
	}
}

//延时以使当前音频记录的播放时间与实际时间同步
if (normalPkt.stream_index == audioIndex)
{
	AVRational audioTimeBase = inFormatCtx->streams[audioIndex]->time_base;
	AVRational currentTimeBase = { 1, AV_TIME_BASE };
	//计算视频播放时间
	int64_t audioTime = av_rescale_q(normalPkt.dts, audioTimeBase, currentTimeBase);
	//计算实际视频的播放时间
	int64_t currentTime = av_gettime() - startTime;
	if (audioTime > currentTime) {
av_usleep((unsigned int)(audioTime - currentTime));
	}
}

3.再次调试运行,mp4视频部分看起来播放正常了。换个mp4试试,好象也正常,但世事哪有那么简单,声音听起来总是有些杂音,这又是怎么回事呢?又经过很长时间的研究学习,我发现:原来是音频流的采样率与扬声器的采样率不一致造成的。要保证音频流听起来正常,就需要保证打开的扬声器的采样率及解码转换后的采样率保证一致才可以。比如,如果mp4文件中音频的采样率为44100,则以下两处的采样率也要做相应修改:

cpp 复制代码
//将音频帧转换到数组
int fmlp::convertAudioFrameToAudioBuff(AVFrame*frame, char**pBuf, int&len){
	int outSampleNum = 0;
	SwrContext* audioSwrCtx = NULL;
	audioSwrCtx = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, AVSampleFormat::AV_SAMPLE_FMT_S16, 44100, AV_CH_LAYOUT_STEREO, (AVSampleFormat)frame->format, frame->sample_rate, NULL, NULL);
	swr_init(audioSwrCtx);
	outSampleNum = swr_convert(audioSwrCtx, (uint8_t**)pBuf, len / frame->channels / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16), (const uint8_t**)frame->data, frame->nb_samples);
	swr_free(&audioSwrCtx);
	return outSampleNum;

}
cpp 复制代码
//打开扬声器
void fmlp::openSpeaker(){
	outWaveform.wFormatTag = WAVE_FORMAT_PCM;
	outWaveform.nSamplesPerSec = 44100;
	outWaveform.wBitsPerSample = 16;
	outWaveform.nChannels = 2;
	//waveform.nBlockAlign = (waveform.wBitsPerSample * waveform.nChannels) / 8;
	outWaveform.nBlockAlign = (outWaveform.wBitsPerSample*outWaveform.nChannels) >> 3;
	outWaveform.nAvgBytesPerSec = outWaveform.nBlockAlign * outWaveform.nSamplesPerSec;
	outWaveform.cbSize = 0;

	waveOutOpen(&hWaveOut, WAVE_MAPPER, &outWaveform, (DWORD)(speakerCallback), 0L, CALLBACK_FUNCTION);
	waveOutSetVolume(hWaveOut, 4 * 0xffffffff);
	waveHdrArr = new WAVEHDR[audioDataArrNum];
	for (int i = 0; i < audioDataArrNum; i++)
	{
		waveHdrArr[i].lpData = new char[finalAudioDataSize];
		waveHdrArr[i].dwBufferLength = finalAudioDataSize;
		waveHdrArr[i].dwBytesRecorded = 0;
		waveHdrArr[i].dwUser = 0;
		waveHdrArr[i].dwFlags = 0;
		waveHdrArr[i].dwLoops = 0;
		waveHdrArr[i].lpNext = NULL;
		waveHdrArr[i].reserved = 0;
		waveOutPrepareHeader(hWaveOut, &waveHdrArr[i], sizeof(WAVEHDR));
	}

}

4.再次调试运行,视频和音频都能正常播放了。

写这篇教程用了不到一个小时,但问题的排查却历尽艰辛,各位同行都有类似的经历吧。

相关推荐
_Twink1e12 小时前
openJiuwen 实训营 Day 1 · WorkSwarm 入门教程
开发语言·c++·openjiuwen
dong_junshuai12 小时前
每天一个开源项目#87 fmt:24.5K Stars 的 C++ 格式化核心
c++·开源·github
雪的季节13 小时前
Multisim14.0的详细中文安装步骤【附安装包】
c++
Persistent的粽子!14 小时前
C++:类与对象(二)
开发语言·c++
蒸蒸yyyyzwd15 小时前
cpp 选手备战秋招学习笔记 day23
c++·求职招聘
.YM.Z15 小时前
C++ STL 容器深度剖析:vector 与 list 的模拟实现及对比
开发语言·c++·vector·list
mengzhi啊16 小时前
QEventLoop loop配合loop.exec();替代while(1)。电脑cpu占用为0,使用while(1)cpu占用率100%
c++·qt
HugoStudio_SWAN17 小时前
洛谷 P1319 / P1320 压缩技术——同一枚硬币的正反面
c++·学习·程序人生·算法
小小晓.17 小时前
C++单例模式万字详解:6种实现演进+线程安全彻底解决+CRTP终极模板
c++·单例模式
j7~18 小时前
【C++11】C++11 新特性全套详解(列表初始化、右值引用、lambda 表达式、function 与 bind 包装器等)
c++·右值引用·lambda表达式·可变参数模板·移动语义·包装器·c++11发展史