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

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

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

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

const char *inFileName = "d:\\mp4\\dtz.mp4";	

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

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

//延时以使当前视频记录的播放时间与实际时间同步
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,则以下两处的采样率也要做相应修改:

//将音频帧转换到数组
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;}
//打开扬声器
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.再次调试运行,视频和音频都能正常播放了。 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/312267.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用anaconda创建notebook工程

1.由于每个工程使用的环境都可能不一样&#xff0c;因此一个好的习惯就是不同的工程都创建属于自己的环境&#xff0c;在anaconda中默认的环境是base&#xff1a; //括号中名字&#xff0c;代表当前的环境 (base)dragonmachine: $ conda create --nameexample2.激活环境 // 环…

Kubernetes网络-VXLAN

一. 网络基础 1. 计算机网络的分层 如今连接方式也越来也丰富&#xff0c;网线、WiFi、蓝牙、光纤&#xff0c;甚至我们普通的电线、照明所用的灯光&#xff0c;都可以作为接入网络的介质。如此庞大的网络&#xff0c;丰富多样的设备&#xff0c;计算机网络技术能把它们统一起…

回味2023

2023年的元旦假期我是在南京度过的&#xff0c;1月2日&#xff0c;我特别前往南京博物院&#xff0c;为了看一个特展——《墨田》。 时代发展&#xff0c;有些东西变了&#xff0c;但是有些东西没有变。 比如书写的方式变了&#xff0c;我们这一代&#xff0c;不再耕耘在真的“…

ubuntu22.04安装anacoda遇到的坑

这几天把用了3年的windows10换成了ubuntu22.04 各种环境都得配置&#xff0c;本文记录下遇到的坑。 1、anacoda在ubuntu上也可以用官方也提供了安装包&#xff0c;但是没有图形界面&#xff0c;需要以命令行的方式安装和运行配置 1.1 安装&#xff1a;官网下载后&#xff0c;…

Django 学习教程-介绍与安装

系列 Django 学习教程-第一个 Django 应用-CSDN博客 介绍 Django 是一个高级 Python Web 框架&#xff0c;它鼓励快速开发和干净、实用的设计。 它由经验丰富的开发人员构建&#xff0c;解决了 Web 开发的大部分麻烦&#xff0c;因此您可以专注于在编写应用程序时无需重新发…

手把手教你绘制和解读实用R列线图(Nomogram):从入门到精通

一、引言 列线图&#xff08;Nomogram&#xff09;是一种常用的数据可视化工具&#xff0c;它能够直观地展示多个变量之间的关系&#xff0c;并帮助我们理解和解释复杂的数据模式。通过绘制列线图&#xff0c;我们可以将各种变量的影响和相互关联转化为图形化的表示&#xff0c…

前端基础(三十七):属性结构数据进行关键字筛选

效果 核心源码 type MenuItem {label: string;key: string | number;icon?: React.ReactNode;children?: MenuItem[];type?: group; }function filterTreeData(tree: MenuItem[], keyword: string): MenuItem[] {return tree.filter((node: MenuItem) > {if (node.labe…

修改一个VC++访问数据库源码

下载一个VC6访问数据库的源码;修改; 打开工程先出现下图错误; 根据资料,出现此错误,解决方法: 1.如果用户不需要在 WizardBar,请关闭该的 WizardBar 并重新启动 Visual C++6.0。 如果但是,您想访问 WizardBar 功能,请关闭受影响的工作区之前关闭所有窗口。 2.重新生…

我的512天创作者纪念日总结:高效、高现

文章目录 512天创作者纪念日&#xff1a;2023年的12月31日CSDN的512天消息提醒第一篇文章&#xff0c;最后一篇文章总计847篇文章&#xff0c;每月发文分布512天&#xff0c;各专栏文章统计512天&#xff0c;互动总成绩 512天创作者纪念日&#xff1a;2023年的12月31日 2023年…

node相关的args属性与<param>子标签的区别

launch文件内&#xff1a;node标签内的<param>标签示例&#xff1a; 可以看到launch文件内的<param>标签在命令行内会转化为--ros-args -p 这样格式的命令&#xff0c;说明<param>标签指定的是ros2内的参数。不能用于传递非ros2的传入参数 如果要传入非ros2…

CentOS:docker同一容器间通信

docker同一容器中不同服务以别名访问 1、创建bridge网络 docker network create testnet 2、查看Docker网络 docker network ls 3、运行容器连接到testnet网络 使用方法&#xff1a;docker run -it --name <容器名> —network --network-alias <网络别名> <…

VMware虚拟机之文件夹共享jdk和tomcat安装防火墙设置

目录 一. 配置文件夹共享功能 1.1 为什么需要配置文件夹共享功能 1.2 配置文件共享功能 1.3 普通共享和高级共享的区别 1.3.1 普通共享 1.3.2 高级共享 1.3.3 总结 二. jdk的配置 2.1 安装jdk 2.2 配置jdk的环境配置jdk 2.3 配置成功 三. TomCat的配置 四. 防火墙设置 4.1…