当前位置: 首页 > article >正文

SDL2 播放音频(MP4)

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.kler.cn/news/134366.html

相关文章:

  • 【linux】进行间通信——共享内存+消息队列+信号量
  • 开源与闭源:创新与安全的平衡
  • STM32CubeMX学习笔记-CAN接口使用
  • Java SPI机制
  • 探索Scrapy中间件:自定义Selenium中间件实例解析
  • JVM-HotSpot虚拟机对象探秘
  • 原理Redis-动态字符串SDS
  • 04 后端增删改查【小白入门SpringBoot + Vue3】
  • 目标检测YOLO实战应用案例100讲-基于机器视觉的水稻病虫害监测预警(续)
  • Git命令总结-常用-后续使用频繁的再添加~
  • RSocket 与 gRPC 基准测试
  • 【开题报告】疫苗在线预约小程序的设计与实现
  • python数据可视化
  • WPF中行为与触发器的概念及用法
  • 2023年【广东省安全员C证第四批(专职安全生产管理人员)】考试题库及广东省安全员C证第四批(专职安全生产管理人员)考试试卷
  • 系统时间和JVM的Date时间不一致问题解决
  • .Net6 部署到IIS示例
  • 【电路笔记】-欧姆定律
  • 100套Axure RP大数据可视化大屏模板及通用组件库
  • Linux环境下C++ 接入OpenSSL
  • 无需添加udid,ios企业证书的自助生成方法
  • 系列九、对象的生命周期和GC
  • Linux虚拟机中网络连接的三种方式
  • MySQL 教程 1.1
  • “ /^A-Z:\\{1,2}^/:\*\?<>\|+\.(jpg|gif|png|bmp)$/i ”这个正则表达式的理解
  • 月子会所信息展示服务预约小程序的作用是什么
  • Git-概念与架构
  • C语言 字符函数汇总,模拟实现各字符函数(炒鸡详细)
  • T10 数据增强
  • 树莓派通过网线连接电脑(校园网也能连接),实现SSH连接