第3课 使用FFmpeg获取并播放音频流

本课对应源文件下载链接:

https://download.csdn.net/download/XiBuQiuChong/88680079

FFmpeg作为一套庞大的音视频处理开源工具,其源码有太多值得研究的地方。但对于大多数初学者而言,如何快速利用相关的API写出自己想要的东西才是迫切需要的,至于原理的进一步学习那是以后的事情。

在上一课中,我们已经成功获取到视频流并显示,这节课我们将参考视频的工作流程来获取音频并播放。

1.与处理视频的过程差不多,要播放音频就要先初始化音频解码器,在函数runFFmpeg中加入以下代码:

//音频解码器
int audioIndex = -1;
AVCodec *aDecodec;
AVCodecContext *aDecodeCtx = NULL;

//初始化并打开音频解码器
aDecodec = avcodec_find_decoder(inFormatCtx->streams[audioIndex]->codecpar->codec_id);
aDecodeCtx = avcodec_alloc_context3(aDecodec);
avcodec_parameters_to_context(aDecodeCtx, inFormatCtx->streams[audioIndex]->codecpar);
avcodec_open2(aDecodeCtx, aDecodec, 0);

2.在处理视频数据包后我们可以接着处理音频数据包,并把音频帧转换为pcm数组加入音频队列备用:

if (normalPkt.stream_index == videoIndex)
		{
			ret = avcodec_send_packet(vDecodeCtx, &normalPkt);
			ret = avcodec_receive_frame(vDecodeCtx, deVideoFrame);
			av_packet_unref(&normalPkt);
			ret = sws_scale(bgrSwsCtx, (const uint8_t* const*)deVideoFrame->data, deVideoFrame->linesize, 0, deVideoFrame->height, bgrFrame.data, bgrFrame.linesize);
			srcMat = cv::Mat(bgrFrame.height, bgrFrame.width, CV_8UC3, bgrFrame.data[0]);
			//imshow("viceo", srcMat);
			//cv::waitKey(10);
			mainDlg->drawMatOfPlay(srcMat);
			av_frame_unref(deVideoFrame);
		}
		else if (normalPkt.stream_index == audioIndex)
		{

			ret = avcodec_send_packet(aDecodeCtx, &normalPkt);
			while (1){
				ret = avcodec_receive_frame(aDecodeCtx, deAudioFrame);
				if (ret != 0){
					break;
				}
				else{

					int originAudioDataSize = deAudioFrame->linesize[0] * deAudioFrame->channels << 1;
					outAudioBuff = new char[originAudioDataSize];
					int outSampleNum = convertAudioFrameToAudioBuff(deAudioFrame, &outAudioBuff, originAudioDataSize);
					int finalAudioDataSize = outSampleNum *av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) *deAudioFrame->channels;
					tmpAudioQueObj.audioDataArr = outAudioBuff;
					tmpAudioQueObj.audioDataSize = finalAudioDataSize;
					EnterCriticalSection(&queLock);
					outAudioQue.push(tmpAudioQueObj);
					if (outAudioQue.size() > 50){
						free(outAudioQue.front().audioDataArr);
						outAudioQue.front().audioDataSize = 0;
						outAudioQue.front().audioDataArr = NULL;
						outAudioQue.front().audioDataSize = NULL;
						outAudioQue.pop();
					}
					LeaveCriticalSection(&queLock);
				}
				av_frame_unref(deAudioFrame);
			}

			av_packet_unref(&normalPkt);

		}

3.为了能播放声音,需要先打开扬声器,然后把队列中的数据送入扬声器:

//打开扬声器
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));
	}

}
//扬声器回调函数
DWORD CALLBACK fmlp::speakerCallback(HWAVEOUT hwaveout, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	switch (uMsg)
	{
	case WOM_OPEN:
		break;

	case WOM_DONE:

	{
					 LPWAVEHDR pwh = (LPWAVEHDR)dwParam1;
					 if (pwh->lpData){
						 free(pwh->lpData);
						 pwh->dwBufferLength = 0;
						 pwh->lpData = NULL;
						 pwh->dwBufferLength = NULL;
					 }
	}


		break;

	case WOM_CLOSE:
		break;
	default:
		break;
	}
	return 0;
}




//播放声音
DWORD WINAPI fmlp::playAudioThreadProc(LPVOID lpParam){
	fmlp *pThis = (fmlp*)lpParam;
	pThis->playAudio();
	return 0;



}

int fmlp::playAudio(){


	int i = 0;
	while (true){

		if (outAudioQue.empty()){
			Sleep(5);
			continue;
		}
		EnterCriticalSection(&queLock);

		if (waveHdrArr[i].dwFlags & WHDR_PREPARED){
			waveHdrArr[i].lpData = (LPSTR)outAudioQue.front().audioDataArr;
			waveHdrArr[i].dwBufferLength = outAudioQue.front().audioDataSize;
			waveOutWrite(hWaveOut, &waveHdrArr[i], sizeof(WAVEHDR));
			outAudioQue.pop();
			i++;
		}
		LeaveCriticalSection(&queLock);
		if (i >= audioDataArrNum){
			i = 0;
		}
		Sleep(5);
	}

}

4.这样一个最简单的既能播放视频也能播放音频的播放器就完成了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于“Python”Django 管理网站的核心知识点整理大全52

目录 注意 18.2.2 激活模型 settings.py 18.2.3 Django 管理网站 1. 创建超级用户 注意 2. 向管理网站注册模型 admin.py 注意 3. 添加主题 Climbing。 18.2.4 定义模型 Entry models.py 18.2.5 迁移模型 Entry 18.2.6 向管理网站注册 Entry admin.py 往期快速…

C++:stack、queue、priority_queue增删查改模拟实现、deque底层原理

C:stack、queue、priority_queue增删查改模拟实现 前言一、Cstack的介绍和使用1.1 引言1.2 satck模拟实现 二、Cqueue的介绍和使用2.1 引言2.2 queue增删查改模拟实现 三、STL标准库中stack和queue的底层结构:deque3.1 deque的简单介绍(了解)3.2 deque的缺陷3.3 为什么选择dequ…

c++哈希表——超实用的数据结构

文章目录 1. 概念引入1.1 整数哈希1.1.1 直接取余法。1.1.2 哈希冲突1.1.2.1 开放寻址法1.1.2.2 拉链法 1.2 字符串哈希 3.结语 1. 概念引入 哈希表是一种高效的数据结构 。 H a s h Hash Hash表又称为散列表&#xff0c;一般由 H a s h Hash Hash函数(散列函数)与链表结构共同…

【代码随想录】刷题笔记Day42

前言 这两天机器狗终于搞定了&#xff0c;一个控制ROS大佬&#xff0c;一个计院编程大佬&#xff0c;竟然真把创新点这个弄出来了&#xff0c;牛牛牛牛&#xff08;菜鸡我只能负责在旁边喊加油&#xff09;。下午翘了自辩课来刷题&#xff0c;这次应该是元旦前最后一刷了&…

车载电子电器架构 —— 电子电气系统开发角色定义

车载电子电器架构 —— 电子电气系统开发角色定义 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 注:本文12000字,深度思考者进!!! 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的…

鸿蒙应用开发 新闻数据加载

1 HTTP 数据请求概述 日常生活中我们使用应用程序看新闻、发送消息等&#xff0c;都需要连接到互联网&#xff0c;从服务端获取数据。例如&#xff0c;新闻应用可以从新闻服务器中获取最新的热点新闻&#xff0c;从而给用户打造更加丰富、更加实用的体验。 那么要实现这样一种…

技术阅读周刊第十二期

年前最后一篇推送&#xff0c;提前祝大家新年快乐。 技术阅读周刊&#xff0c;每周更新。 历史更新 20231201&#xff1a;第八期20231215&#xff1a;第十期20231122&#xff1a;第十一期 Deno vs Go: Native hello world performance | Tech Tonic URL: https://medium.com/de…

R306指纹识别模块的硬件接口

1.外部接口尺寸图 采集芯片外形尺寸&#xff1a;33.4*20.4*3.79 mm 2.串行通讯 R306 指纹模块通讯接口定义&#xff1a; 3.USB 通讯 4.接口说明 4.1 UART a) UART 缺省波特率为 57.6kbps&#xff0c;数据格式&#xff1a;8 位数据位&#xff08;低位在前&#xff09;&#…

【leetcode100-020】【矩阵】旋转图像

【题干】 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 【思路】 怎么还整上小学奥数题了&#xff08;不是对角翻转水平/垂…

什么是SEO?

什么是SEO&#xff1f; SEO代表“搜索引擎优化”。这是通过非付费&#xff08;也称为“自然”&#xff09;搜索引擎结果来提高网站流量的质量和数量以及品牌曝光率的做法。 尽管有首字母缩略词&#xff0c;但 SEO 既关乎搜索引擎本身&#xff0c;也关乎人。这是关于了解人们在…

设计模式(4)--类行为(10)--模板方法

1. 意图 定义一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。 模板方法使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 2. 两种角色 抽象类(Abstract Class)、具体类(Concrete Class) 3. 优点 3.1 一种代码复用的基本技术。提取公共行为&am…

现代建筑 Modern Design 展示前端界面html推荐

前言 一款展示现代建筑的网页 一、需求分析 一个现代建筑展示网站是指利用现代技术和设计风格&#xff0c;以展示和推广各种建筑项目和设计作品为主要目的的网站。 作为一个现代建筑展示网站&#xff0c;以下是一些需要的要素&#xff1a; 响应式设计&#xff1a;现代建筑展…

mixins混淆请求字典封装库

摘要&#xff1a; 页面请求要使用到很多重点的查询&#xff0c;写在本页面的逻辑代码太混乱&#xff0c;所以可以抽离封装成功一个js库混淆进来&#xff01; commonMixins.js: import {Toast} from "vant"; export const oplistMix {mounted() {this.GETSTORE_LOCA…

基于电商场景的高并发RocketMQ实战-Consumer端队列负载均衡分配机制、并发消费以及消费进度提交

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 【11来了】文章导读地址&#xff1a;点击查看文章导读&#xff01; &#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f3…

迅软科技助力高科技防泄密:从华为事件中汲取经验教训

近期&#xff0c;涉及华为芯片技术被窃一事引起广泛关注。据报道&#xff0c;华为海思的两个高管张某、刘某离职后成立尊湃通讯&#xff0c;然后以支付高薪、股权支付等方式&#xff0c;诱导多名海思研发人员跳槽其公司&#xff0c;并指使这些人员在离职前通过摘抄、截屏等方式…

MFC - 给系统菜单(About Dialog)发消息

文章目录 MFC - 给系统菜单(About Dialog)发消息概述笔记resource.h菜单的建立菜单项的处理MSDN上关于系统菜单项值的说法END MFC - 给系统菜单(About Dialog)发消息 概述 做了一个对话框程序, 在系统菜单(在程序上面的标题栏右击)中有"关于"的菜单. 这个是程序框架…

4.24 构建onnx结构模型-Slice

前言 构建onnx方式通常有两种&#xff1a; 1、通过代码转换成onnx结构&#xff0c;比如pytorch —> onnx 2、通过onnx 自定义结点&#xff0c;图&#xff0c;生成onnx结构 本文主要是简单学习和使用两种不同onnx结构&#xff0c; 下面以 Slice 结点进行分析 方式 方法一…

Grafana增加仪表盘

1.Grafana介绍 grafana 是一款采用Go语言编写的开源应用&#xff0c;主要用于大规模指标数据的可视化展现&#xff0c;是网络架构和应用分析中最流行的时序数据展示工具&#xff0c;目前已经支持绝大部分常用的时序数据库。 Grafana下载地址&#xff1a;https://grafana.com/g…

网际协议IPv4

基本介绍 网际协议IP是TCP/IP体系中两个重要的协议之一。IPv4虽有最终被IPv6取代的趋势&#xff0c;但它仍是当前使用的最重要的因特网协议。 与IP配套使用的还有3个协议&#xff1a; 地址解析协议ARP(Address Resolution Protocol)因特网控制报文协议ICMP(Internet Control …

年终跑步总结

第一个365天无间断年 以前也跑步很频繁&#xff0c;但今年是第一次365天未缺勤。年跑步量也是历来个人最多&#xff1a;2900km以上。 连续跑步天数累积超700天了 这里出现的签到天数累加只有666次&#xff0c;因为中间有跑步、但没有到app上签到&#xff0c;实际最近一次停…