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博客