当前位置:网站首页>ffmpeg+SDL2实现音频播放
ffmpeg+SDL2实现音频播放
2022-06-25 06:42:00 【师范大学生】
本文记录使用ffmpeg+SDL2进行视频文件内的音频播放,注意是播放视频文件内的音频,不是播放音频文件。
本文使用的ffmpeg版本为5.0.1,SDL的版本为2.022。c++环境为vs2017。
和之前的播放视频或音频明显的区别是,播放视频文件内的音频需要进行重采样操作,代码中会引入重采样结构体SwrContext。
重采样结构体能够改变原先音频的采样率、声道数等参数,令各种音频能够按照我们设定的参数进行输出。这样做的原因是不同视频文件内的音频参数通常区别较大,如果分别处理工作量太大,不如将其统一成相同的格式。
先上整体代码:
#include <stdio.h>
#include <tchar.h>
#include <iostream>
extern "C"
{
#include "SDL.h" //头文件不仅要在项目中鼠标点击配置,在代码中也要引入
#include "include/libavformat/avformat.h"
#include "include/libavformat/avformat.h"
#include "include/libswscale/swscale.h"
#include "include/libavdevice/avdevice.h"
#include "include/libavcodec/avcodec.h"
#include "include/libswresample/swresample.h"
};
using namespace std;
#define MAX_AUDIO_FRAME_SIZE 19200
static Uint8 *audio_chunk; //音频块
static Uint32 audio_len; //音频剩下的长度
static Uint8 *audio_pos; //音频当前的位置
//回调函数的作用是填充音频缓冲区,当音频设备需要更多数据的时候会调用该回调函数
//userdata一般不使用,stream是音频缓冲区,len是音频缓冲区大小
void fill_audio(void *udata, Uint8 *stream, int len) {
//将stream置0,SDL2必须有的操作
SDL_memset(stream, 0, len);
if (audio_len == 0) //如果没有剩余数据
return;
len = (len > audio_len ? audio_len : len); //尽可能得到更多的数据
//混音处理
SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
audio_pos += len; //更新音频当前的位置
audio_len -= len; //更新音频剩下的长度
}
#undef main
int main(int argc, char* argv[])
{
const char *audio_path = "ds.mov";//视频路径
//初始化ffmpeg组件
AVFormatContext *pFormatCtx = nullptr;
int i, audioStream = -1,videoStream = -1;
AVCodecParameters *pCodecParameters = nullptr;
AVCodecContext *pCodecCtx = nullptr;
const AVCodec *pCodec = nullptr;
AVFrame *pFrame = nullptr;
AVPacket *packet;
uint8_t *out_buffer;
int64_t in_channel_layout;
//初始化重采样组件
struct SwrContext *au_convert_ctx;
//打开音频文件 ffmpeg
if (avformat_open_input(&pFormatCtx, audio_path, nullptr, nullptr) != 0) {
cout << "can not open the audio!" << endl;
return -1;
}
//寻找视频流与音频流 ffmpeg
audioStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (audioStream == -1) {
cout << "can not find audio stream!" << endl;
return -1;
}
videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (videoStream == -1) {
cout << "can not open a video stream" << endl;
return -1;
}
//寻找解码器 ffmpeg
pCodecParameters = pFormatCtx->streams[audioStream]->codecpar;
pCodec = avcodec_find_decoder(pFormatCtx->streams[audioStream]->codecpar->codec_id);
if (pCodec == nullptr) {
cout << "can not find a codec" << endl;
return -1;
}
//加载解码器参数 ffmpeg
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_parameters_to_context(pCodecCtx, pCodecParameters) != 0) {
cout << "can not copy codec context" << endl;
return -1;
}
//启动解码器 ffmpeg
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
cout << "can not open the decoder!" << endl;
return -1;
}
//初始化packet与pframe ffmpeg
packet = (AVPacket*)av_malloc(sizeof(AVPacket));
av_packet_alloc();
pFrame = av_frame_alloc();
//音频参数初始化
uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;//输出声道
int out_nb_samples = 1024; //音频缓冲区的采样个数
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//输出格式S16
int out_sample_rate = 44100; //pcm采样率
int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);//声道数量
int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);//计算音频缓冲区大小
out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);//初始化音频缓冲区大小
//初始化SDL
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
cout << "can not open the SDL!" << endl;
return -1;
}
//设置音频相关参数
SDL_AudioSpec spec;
spec.freq = out_sample_rate; //pcm采样频率
spec.format = AUDIO_S16SYS; //音频格式,16bit
spec.channels = out_channels; //声道数量
spec.silence = 0; //设置静音的值
spec.samples = out_nb_samples;//音频缓冲区的采样个数
spec.callback = fill_audio; //回调函数
spec.userdata = pCodecCtx; //回调函数的数据来自解码器
//初始化SDL
if (SDL_OpenAudio(&spec, nullptr) < 0) {
cout << "can not open audio" << endl;
return -1;
}
//音频重采样操作
in_channel_layout = av_get_default_channel_layout(pCodecCtx->channels);
cout << "in_channel_layout: " << in_channel_layout << endl;
au_convert_ctx = swr_alloc(); //初始化重采样结构体
au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt, out_sample_rate, in_channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0, NULL);//重采样结构体赋值
swr_init(au_convert_ctx);//将重采样结构体参数加载
SDL_PauseAudio(0);
//循环读取packet
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == audioStream) {
//进行解码操作
avcodec_send_packet(pCodecCtx, packet);
while (avcodec_receive_frame(pCodecCtx,pFrame) == 0)
{
//将输入的音频按照定义的参数进行转换,并输出
swr_convert(au_convert_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)pFrame->data, pFrame->nb_samples);
}
audio_chunk = (Uint8*)out_buffer; //设置音频缓冲区
audio_len = out_buffer_size; //更新音频长度
audio_pos = audio_chunk; //更新音频位置
while (audio_len > 0)
{
SDL_Delay(1);//延迟播放
}
}
av_packet_unref(packet);//清空packet内容
}
swr_free(&au_convert_ctx);//清空重采样结构体内容
//回收ffmpeg组件
if (pFrame) {
av_frame_free(&pFrame);
pFrame = nullptr;
}
if (pCodecCtx) {
avcodec_close(pCodecCtx);
pCodecCtx = nullptr;
pCodec = nullptr;
}
if (pFormatCtx) {
avformat_close_input(&pFormatCtx);
pFormatCtx = nullptr;
}
//关闭SDL
SDL_Quit();
cout << "succeed!" << endl;
return 0;
}
从代码中可以看到,除了ffmpeg与sdl相关的老内容之外,代码中增加了swr的重采样内容。
//初始化重采样组件
struct SwrContext *au_convert_ctx;
首先对重采样组件进行初始化操作,该结构体会记录重采样后音频的各种参数。
au_convert_ctx = swr_alloc(); //初始化重采样结构体
au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt, out_sample_rate, in_channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0, NULL);//重采样结构体赋值
swr_init(au_convert_ctx);//将重采样结构体参数加载
在结构体设置参数的时候,格式和采样率要和原先视频中的音频一致,因此需要在启动解码器之后再进行重采样组件的参数设置。
//将输入的音频按照定义的参数进行转换,并输出
swr_convert(au_convert_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)pFrame->data, pFrame->nb_samples);
在循环操作中,使用以上函数进行音频的重采样转换以及输出,可见也简化了输出所需要的代码内容。
边栏推荐
- NPM install reports an error: gyp err! configure error
- NSIS silent installation vs2013 runtime
- STL tutorial 4- input / output stream and object serialization
- C# 读取web上的xml
- 消息中间件之ActiveMQ的基本使用
- Accès à la boîte aux lettres du nom de domaine Lead à l'étranger
- Keil and Proteus joint commissioning
- Mysql面试-执行sql响应比较慢,排查思路。
- WinForm implementation window is always at the top level
- Technology blog | how to communicate using SSE
猜你喜欢
ts环境搭建
三台西门子消防主机FC18配套CAN光端机进行光纤冗余环网组网测试
取消word文档中某些页面的页眉
This article uses pytorch to build Gan model!
用函数的递归来解决几道有趣的题
One "stone" and two "birds", PCA can effectively improve the dilemma of missing some ground points under the airborne lidar forest
[distillation] pointdistiller: structured knowledge distillationwards efficient and compact 3D detection
How to use ad wiring for PCB design?
【QT】Qt 5 的程序:打印文档
海思3559 sample解析:vio
随机推荐
【QT】qtcreator便捷快捷键以及QML介绍
npm install 报错 : gyp ERR! configure error
php入门基础记录
单位转换-毫米转像素-像素转毫米
Sword finger offer II 027 Palindrome linked list
OAuth 2.0一键登录那些事
环网冗余式CAN/光纤转换器的CAN光端机在消防火灾联网报警系统中的应用
权限、认证系统相关名词概念
el-input实现尾部加字
opencv最小值滤波(不局限于图像)
Collection of common terms and meanings in forestry investigation based on lidar
挖掘微生物暗物质——新思路
1464. 数组中两元素的最大乘积
SSL证书免费获取教程
Analysis of kinsing dual platform mining family virus
神经网络与深度学习-3- 机器学习简单示例-PyTorch
Runtime——methods成员变量,cache成员变量
Take you through the normalization flow of GaN
WinForm实现窗口始终在顶层
How to use printf of 51 single chip microcomputer