SDL2 播放音频(MP4)

news2024/10/7 14:29:06

1.简介

这里引入FFmpeg库,获取音频流数据,然后通过FFmpeg将视频流解码成pcm原始数据,再将pcm数据送入到SDL库中实现音频播放。

2.FFmpeg的操作流程

  • 注册API:av_register_all()
  • 构建输入AVFormatContext上下文:avformat_open_input()
  • 查找音视频流信息:avformat_find_stream_info()
  • 查找解码器:avcodec_find_decoder()
  • 打开解码器:avcodec_open2()
  • 然后通过while循环,不停的读取数据:av_read_frame()
  • 帧解码:avcodec_send_packet()和avcodec_receive_frame()
  • 重采样:swr_convert()

3.SDL音频播放流程

SDL播放音频的流程如下:

  • 初始化音频子系统:SDL_Init()。
  • 设置音频参数:SDL_AudioSpec。
  • 设置回调函数:SDL_AudioCallback。
  • 打开音频设备:SDL_OpenAudio()。
  • 打开pcm文件,读取数据。
  • 开始播放:SDL_PauseAudio()。

4.示例

#include <stdio.h>
#include <SDL.h>
#include <memory>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libswresample/swresample.h"
};

static  Uint8  *audio_chunk;
static  Uint32  audio_len;
static  Uint8  *audio_pos;

void  fill_audio(void *udata, Uint8 *stream, int len)
{
	//SDL 2.0
	SDL_memset(stream, 0, len);
	if (audio_len == 0)		/*  Only  play  if  we  have  data  left  */
		return;
	len = (len > audio_len ? audio_len : len);	/*  Mix  as  much  data  as  possible  */

	SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME/2);
	audio_pos += len;
	audio_len -= len;	
}

AVFrame *recv(AVCodecContext *codecCtx)
{
	if (!codecCtx)
	{
		return NULL;
	}

	AVFrame *frame = av_frame_alloc();
	int ret = avcodec_receive_frame(codecCtx, frame);

	if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
	{
		av_frame_free(&frame);
		return NULL;
	}
	else if (ret < 0)
	{
		av_frame_free(&frame);
		return NULL;
	}

	return frame;
}

#undef main
int main(int argc, char* argv[])
{
	av_register_all();

	///ffmpeg
	avformat_network_init();

	AVFormatContext* pFormatCtx = NULL;
	const char* inputUrl = "./2.mp4";

	///打开输入的流
	int ret = avformat_open_input(&pFormatCtx, inputUrl, NULL, NULL);
	if (ret != 0)
	{
		printf("Couldn't open input stream.\n");
		return -1;
	}

	//查找流信息
	if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
	{
		printf("Couldn't find stream information.\n");
		return -1;
	}

	//找到音频流索引
	int audio_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
	AVStream* st = pFormatCtx->streams[audio_index];
	AVCodec* codec = nullptr;
	//找到解码器
	codec = avcodec_find_decoder(st->codecpar->codec_id);
	if (!codec)
	{
		fprintf(stderr, "Codec not found\n");
		return -1;
	}

	//申请AVCodecContext
	AVCodecContext* pCodecCtx = avcodec_alloc_context3(codec);
	if (!pCodecCtx)
	{
		return -1;
	}

	avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[audio_index]->codecpar);

	//打开解码器
	if ((ret = avcodec_open2(pCodecCtx, codec, NULL) < 0))
	{
		return -1;
	}

	AVPacket* pkt = av_packet_alloc();

	//------------SDL----------------
	//Output Info-----------------------------
	printf("---------------- File Information ---------------\n");
	av_dump_format(pFormatCtx, 0, inputUrl, 0);
	printf("-------------------------------------------------\n");

	SwrContext *swrContext = swr_alloc();
	if (!swrContext)
	{
		return -1;
	}

	swrContext = swr_alloc_set_opts(NULL,													//ctx
		AV_CH_LAYOUT_STEREO,																	//输出channel布局
		AV_SAMPLE_FMT_S16,																		 //输出的采样格式
		44100,																									//采样率
		av_get_default_channel_layout(pCodecCtx->channels),						//输入channel布局
		pCodecCtx->sample_fmt,																	//输入的采样格式
		pCodecCtx->sample_rate,																	//输入的采样率
		0, NULL);

	// 初始化重采样上下文
	if (swr_init(swrContext) < 0)
	{
		swr_free(&swrContext);
		return -1;
	}


	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
	{
		printf("Could not initialize SDL - %s\n", SDL_GetError());
		return -1;
	}

	//SDL_AudioSpec
	SDL_AudioSpec wanted_spec;
	wanted_spec.freq = 44100;
	wanted_spec.format = AUDIO_S16SYS;
	wanted_spec.channels = 2;
	wanted_spec.silence = 0;
	wanted_spec.samples = 1024;
	wanted_spec.callback = fill_audio;
	wanted_spec.userdata = pCodecCtx;


	if (SDL_OpenAudio(&wanted_spec, NULL) < 0)
	{
		printf("can't open audio.\n");
		return -1;
	}

	//Play
	SDL_PauseAudio(0);

	// 分配输出音频数据
	uint8_t	 *out_buffer = nullptr;
	while (av_read_frame(pFormatCtx, pkt) >= 0)
	{
		if (pkt->stream_index == audio_index)
		{
			//一次send 多次recv
			int ret = avcodec_send_packet(pCodecCtx, pkt);
			if (ret < 0)
				continue;

			//释放资源
			av_packet_unref(pkt);

			while (1)
			{
				AVFrame *frame = recv(pCodecCtx);
				if (!frame)
					break;

				//输入的样本数
				int in_nb_samples = frame->nb_samples;//1024

				int out_linesize;
				int dst_nb_samples = av_rescale_rnd(in_nb_samples, 44100, frame->sample_rate, AV_ROUND_UP);

				//输出的样本数
				int out_buffer_size = av_samples_get_buffer_size(NULL, 2, dst_nb_samples, AV_SAMPLE_FMT_S16, 0);

				if(!out_buffer)
					out_buffer = (uint8_t *)av_malloc(out_buffer_size);

				//返回每个通道输出的样本数,错误时为负值
				int sampleCount = swr_convert(swrContext, &out_buffer, dst_nb_samples,
					(const uint8_t**)frame->data, in_nb_samples);

				if (sampleCount <= 0)
				{
					av_frame_free(&frame);
					break;
				}

				int outSize = sampleCount * 2 * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);

				while (audio_len > 0)//Wait until finish
					SDL_Delay(1);

				//Set audio buffer (PCM data)
				audio_chunk = (Uint8 *)out_buffer;
				//Audio buffer length
				audio_len = outSize;
				audio_pos = audio_chunk;

				av_frame_free(&frame);
			}
		}
		else
		{
			//释放资源
			av_packet_unref(pkt);
		}
	}

	//--------------
	av_free(out_buffer);
	av_packet_free(&pkt);
	swr_close(swrContext);
	swr_free(&swrContext);
	avcodec_close(pCodecCtx);
	avcodec_free_context(&pCodecCtx);
	avformat_close_input(&pFormatCtx);

	SDL_CloseAudio();
	SDL_Quit();
}

 5.相关推荐

[总结]FFMPEG视音频编解码零基础学习方法_零基础ffmpeg 雷霄骅-CSDN博客 

FFmpeg 音频解码(秒懂)-CSDN博客

SDL2 播放音频数据(PCM)-CSDN博客

SDL2 消息循环和事件响应-CSDN博客 

SDL2 播放视频文件(MP4)-CSDN博客 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

超细致Python自动化测试实现的思路

Python自动化测试常用于Web应用、移动应用、桌面应用等的测试 同时&#xff0c;我也为大家准备了一份软件测试视频教程&#xff08;含面试、接口、自动化、性能测试等&#xff09;&#xff0c;就在下方&#xff0c;需要的可以直接去观看&#xff0c;也可以直接点击文末小卡片免…

Python自动化测试之request库详解(三)

做过接口测试的都会发现&#xff0c;现在的接口都是HTTPS协议了&#xff0c;今天就写一篇如何通过request发送https请求。 什么是HTTPS HTTPS 的全称是Hyper Text Transfer Protocol over Secure Socket Layer &#xff0c;是以安全为目标的HTTP通道&#xff0c;简单的讲是HTT…

LeetCode【41】缺失的第一个正数

题目&#xff1a; 分析&#xff1a; 第i个位置的数&#xff0c;如果再数组 0到length-1范围内&#xff0c;则将其放到对应的位置&#xff1b; 再遍历一遍数组&#xff0c;找到第一个不在位置i的正数数字&#xff0c;即为所求 思路&#xff1a;https://blog.csdn.net/weixin_45…

基于JavaWeb+SpringBoot+Vue医疗器械商城微信小程序系统的设计和实现

基于JavaWebSpringBootVue医疗器械商城微信小程序系统的设计和实现 源码获取入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 前言 摘 要 目前医疗器械行业作为医药行业的一个分支&#xff0c;发展十分迅速。…

【Java 进阶篇】JQuery 遍历 —— `each()` 方法的奇妙之旅

在前端的世界里&#xff0c;操作元素是我们开发者最为频繁的任务之一。为了更好地操控页面上的元素&#xff0c;JQuery 提供了许多强大的工具&#xff0c;其中 each() 方法是一颗璀璨的明星。本文将深入探讨 each() 方法的原理和用法&#xff0c;带你踏上一场遍历之旅。 起步&…

第四代智能井盖传感器:万宾科技智能井盖位移监测方式一览

现在城市化水平不断提高&#xff0c;每个城市的井盖遍布在城市的街道上&#xff0c;是否能够实现常态化和系统化的管理&#xff0c;反映了一个城市治理现代化水平。而且近些年来住建部曾多次要求全国各个城市加强相关的井盖管理工作&#xff0c;作为基础设施重要的一个组成部分…

JAVA安全之Shrio550-721漏洞原理及复现

前言 关于shrio漏洞&#xff0c;网上有很多博文讲解&#xff0c;这些博文对漏洞的解释似乎有一套约定俗成的说辞&#xff0c;让人云里来云里去&#xff0c;都没有对漏洞产生的原因深入地去探究..... 本文从现象到本质&#xff0c;旨在解释清楚Shrio漏洞是怎么回事&#xff01…

Java学习之路 —— IO、特殊文件

文章目录 1. I/O1.1 常用API1.2 I/O流1.2.1 字节流1.2.2 try-catch-finally和try-with-resource1.2.3 字符流1.2.4 其他的一些流 2. I/O框架3. 特殊文件3.1. Properties3.2 XML 1. I/O 1.1 常用API // 1. 创建文件对象File file new File("E:\\ComputerScience\\java\\…

《C++避坑神器·二十三》C++异常处理exception

有些时候无法设置弹出提示信息或者发送提示信息&#xff0c;时候可以抛出异常来提示各种情况 定义自己的异常 GetPostion()函数内部抛出了异常&#xff0c;所以在捕获异常的时候try要把这个函数包住&#xff0c; Catch()里面写throw后面的类&#xff0c;然后catch内部通过调…

MIB 操作系统Lab: Xv6 and Unix utilities(1)boot xv6

从github中下载xv6代码 $ git clone git://g.csail.mit.edu/xv6-labs-2023 $ cd xv6-labs-2023 编译和运行xv6: $ make qemu 如果在终端输入ls命令&#xff0c;能看到输出。 大多数都是可以直接运行的命令。 xv6没有ps命令&#xff0c;但是可以输入ctrl-p可以看到进程的信…

不加家长好友,如何私密发成绩?

身为老师的你&#xff0c;是否经常收到家长们的询问&#xff0c;要求你告知他们孩子的成绩&#xff1f;而你却因为规定&#xff0c;不能直接将成绩公布&#xff1f;那么&#xff0c;如何解决这个问题呢&#xff1f; 成绩查询系统。是专门为学生和家长提供成绩查询服务的系统。可…

LeetCode - #89 格雷编码

文章目录 前言1. 描述2. 示例3. 答案关于我们 前言 我们社区陆续会将顾毅&#xff08;Netflix 增长黑客&#xff0c;《iOS 面试之道》作者&#xff0c;ACE 职业健身教练。&#xff09;的 Swift 算法题题解整理为文字版以方便大家学习与阅读。 LeetCode 算法到目前我们已经更新…

转录组测序学习第二弹

安装软件 前面已经安装好了conda&#xff0c;那么我们现在需要安装我们后续需要用到的软件 1.先进入我们前面建立的虚拟环境中 conda activate my_env2.安装软件 conda install -y sra-tools conda install -y trimmomatic conda install -y cutadapt multiqc conda install…

(Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码&#xff1a; 四、完整代码数据说明手册&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matalb平台…

docker数据卷详细讲解及数据卷常用命令

docker数据卷详细讲解及数据卷常用命令 Docker 数据卷是一种将宿主机的目录或文件直接映射到容器中的特殊目录&#xff0c;用于实现数据的持久化和共享。Docker 数据卷有以下特点&#xff1a; 数据卷可以在一个或多个容器之间共享和重用&#xff0c;不受容器的生命周期影响。…

易点易动固定资产管理系统:提升企业固定资产领用效率的智慧选择

在现代企业管理中&#xff0c;固定资产的有效管理对于企业的运营和发展至关重要。然而&#xff0c;传统的固定资产领用流程常常繁琐、低效&#xff0c;导致领用效率低下、信息不透明等问题。为了帮助企业解决这些难题&#xff0c;易点易动固定资产管理系统应运而生。本文将介绍…

基于SpringBoot+Vue的新能源汽车充电桩管理系统

基于SpringBootVue的新能源汽车充电桩管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 充电桩详情 管理员界面 摘要 本项目是基于Spring Boot 和 …

C++ 之字符串、字符数组与字符指针(*、**)

C 之字符串、字符数组与字符指针(*、**) 最近频繁使用字符串指针&#xff0c;有时候想取值或者复制&#xff0c;常用到问题&#xff0c;在此总结一下字符串的处理、指针的使用长期更新版~ 1. char 使用相关 1.1 内存使用 首先介绍一下C语言中的数据类型&#xff1a; 下图给…

如何实时提取微信群收到的二维码图片?

10-4 在有些工作中&#xff0c;需要实时提取在微信中收到的二维码图片&#xff0c;比如微信里有一百个群&#xff0c;怎么才能知道这些群里发了二维码出来&#xff0c;要实现这样的功能&#xff0c;微信本身并不提供&#xff0c;但是可以通过一些其它技巧完成。 大概的原理是…