简介
这里实现了一个简单的纯语音播放器,其数据流图如下
图中已经表述的很清楚了,解复用、解码、重采样都通过ffmpeg完成,而播放则由SDL完成。其中解复用、解码和重采样部分可以参看ffmpeg给出的示例文件(解复用和解码可参考demuxing_decoding.c
,重采样可参考resampling_audio.c
)。
由于SDL并不能完全支持所有的语音数据存储格式,因此这里需要将ffmpeg解码出来的语音数据存储格式通过重采样转化为SDL支持的数据存储格式。
头文件
解复用需要用到libavformat
库,解码需要用到libavcodec
库,而重采样需要用到libswresample
和libavutil
库,播放需要用到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;
}