FFmpeg+SDL纯语音播放器

本文详细介绍了如何通过ffmpeg进行音频解复用、解码,然后利用SDL进行重采样和播放,涉及关键步骤如解码器初始化、音频数据处理和SDL回调。适合深入理解音频处理与跨库集成的开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

这里实现了一个简单的纯语音播放器,其数据流图如下
在这里插入图片描述

图中已经表述的很清楚了,解复用、解码、重采样都通过ffmpeg完成,而播放则由SDL完成。其中解复用、解码和重采样部分可以参看ffmpeg给出的示例文件(解复用和解码可参考demuxing_decoding.c,重采样可参考resampling_audio.c)。

由于SDL并不能完全支持所有的语音数据存储格式,因此这里需要将ffmpeg解码出来的语音数据存储格式通过重采样转化为SDL支持的数据存储格式。


头文件

解复用需要用到libavformat库,解码需要用到libavcodec库,而重采样需要用到libswresamplelibavutil库,播放需要用到SDL库,关于ffmpeg+SDL工程的配置可以参考之前写过的一篇文章

由于这里创建了main.cpp源文件,而ffmpeg是用C写的,因此在包含头文件时要做些处理

#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>

#ifdef __cplusplus
}
#endif
#include <SDL.h>
using namespace std;

源码

#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>

#ifdef __cplusplus
}
#endif
#include <SDL.h>
using namespace std;
#define USER_40MS_FRESH (SDL_USEREVENT + 1)

static  Uint8* g_audio_chunk;
static  Uint32 g_audio_len;
static  Uint8* g_audio_pos;

//-- 语音播放回调函数
void AudioPlay(void* udata, Uint8* stream, int len) {
	SDL_memset(stream, 0, len);
	/*  Only  play  if  we  have  data  left  */
	if (g_audio_len == 0)
		return;
	/*  Mix  as  much  data  as  possible  */
	len = (len > g_audio_len ? g_audio_len : len);
	SDL_MixAudio(stream, g_audio_pos, len, SDL_MIX_MAXVOLUME);
	g_audio_pos += len;
	g_audio_len -= len;
}
//-- 解码器初始化函数
int InitCodec(AVFormatContext* fmt_ctx, AVMediaType type, AVCodecContext*& dec_ctx, AVStream*& videoStream, int& streamIdx)
{
	int ret = 0;
	const AVCodec* dec = NULL;
	//-- 查找指定媒体流信息
	streamIdx = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
	if (streamIdx < 0)
	{
		cout << "find stream error!" << endl;
		return -1;
	}
	videoStream = fmt_ctx->streams[streamIdx];
	//-- 获取解码器
	dec = avcodec_find_decoder(videoStream->codecpar->codec_id);
	if (dec == NULL)
	{
		cout << "find decoder error!" << endl;
		return -1;
	}
	//-- 创建解码器
	dec_ctx = avcodec_alloc_context3(dec);
	if (dec_ctx == NULL)
	{
		cout << "create decoder error!" << endl;
		return -1;
	}
	//-- 配置解码器
	ret = avcodec_parameters_to_context(dec_ctx, videoStream->codecpar);
	if (ret < 0)
	{
		cout << "config decoder failure!" << endl;
		return -1;
	}
	//--- 打开解码器
	ret = avcodec_open2(dec_ctx, dec, NULL);
	if (ret != 0)
	{
		cout << "open decoder error!" << endl;
		return -1;
	}
	return 1;

}
int main(int argc, char** argv)
{
	//-- 解码解复用
	AVFormatContext* fmt_ctx = NULL;
	AVCodecContext* audio_dec_ctx = NULL;
	int audio_stream_id = -1;
	AVStream* audio_stream = NULL;
	AVCodec* dec = NULL;
	AVPacket* pkt = NULL;
	AVFrame* frame = NULL;
	//-- 重采样相关
	SwrContext* resample_context = NULL;
	int out_linesize;
	int out_buffer_size;
	uint8_t* out_buffer = NULL;

	const char* fileName;

	if (argc != 2)
	{
		cout << "The number if input parameter error" << endl;
		return 1;
	}
	fileName = argv[1];
	
	//-- 打开文件,并初始化fmt_ctx
	avformat_open_input(&fmt_ctx, fileName, NULL, NULL);
	//-- 读取流信息
	avformat_find_stream_info(fmt_ctx, NULL);
	//-- 打印流信息
	av_dump_format(fmt_ctx, 0, fileName, 0);
	//-- 初始化编码
	InitCodec(fmt_ctx, AVMEDIA_TYPE_AUDIO, audio_dec_ctx, audio_stream, audio_stream_id);

	//-- 获取数据存储空间大小
	out_buffer_size = av_samples_get_buffer_size(&out_linesize, audio_dec_ctx->channels, audio_dec_ctx->frame_size, audio_dec_ctx->sample_fmt, 1);
	//-- 分配重采样后的数据存储空间
	out_buffer = new uint8_t[out_buffer_size];

	//-- 设置重采样参数
	resample_context = swr_alloc_set_opts(NULL, audio_dec_ctx->channel_layout, AV_SAMPLE_FMT_S16, audio_dec_ctx->sample_rate,
		audio_dec_ctx->channel_layout, audio_dec_ctx->sample_fmt, audio_dec_ctx->sample_rate, 0, NULL);
	//-- 初始化重采样对象
	swr_init(resample_context);
	
	//-- 初始化SDL
	SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER);
	//-- 设置SDL语音播放参数
	SDL_AudioSpec spec;
	spec.freq = audio_dec_ctx->sample_rate;
	spec.samples = audio_dec_ctx->frame_size;
	spec.format = AUDIO_S16SYS;
	spec.channels = 2;
	spec.silence = 0;
	spec.userdata = audio_dec_ctx;
	spec.callback = AudioPlay;	//-- 设置语音播放回调函数,用于SDL读取播放数据
	//-- 打开SDL语音播放器
	if (SDL_OpenAudio(&spec, NULL) < 0)
	{
		return 1;
	}
	
	//-- 分配包空间和帧空间
	frame = av_frame_alloc();
	pkt = av_packet_alloc();
	//-- 主循环
	while (av_read_frame(fmt_ctx, pkt) >= 0)
	{
		//-- 筛选语音包
		if (pkt->stream_index == audio_stream_id)
		{
			//-- 将语音包发送到解码器
			avcodec_send_packet(audio_dec_ctx, pkt);
			//-- 读取解码帧
			while (avcodec_receive_frame(audio_dec_ctx, frame) >= 0)
			{
				//-- 语音重采样
				swr_convert(resample_context, &out_buffer, out_linesize, (const uint8_t**)frame->extended_data, frame->nb_samples);
				//-- 记录播放数据的长度、记录数据存储的首地址
				g_audio_chunk = out_buffer;
				g_audio_len = out_linesize;
				g_audio_pos = g_audio_chunk;
				//-- 播放语音
				SDL_PauseAudio(0);
				//-- 等待当前帧的语音数据播放完毕
				while (g_audio_len > 0)
				{
					SDL_Delay(1);
				}
				av_frame_unref(frame);
			}
			av_frame_unref(frame);
		}
		av_packet_unref(pkt);
	}
	//-- 释放资源
	SDL_CloseAudio(); 
	av_frame_unref(frame);
	av_packet_unref(pkt);
	delete[] out_buffer;
	swr_free(&resample_context);
	avcodec_free_context(&audio_dec_ctx);
	avformat_close_input(&fmt_ctx);
	av_frame_free(&frame);
	av_packet_free(&pkt);

	return 0;
}

输入参数配置

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值