当前位置:网站首页>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);在循环操作中,使用以上函数进行音频的重采样转换以及输出,可见也简化了输出所需要的代码内容。
边栏推荐
- 微信小程序开通客服消息功能开发
- realsense d455 semantic_ Slam implements semantic octree mapping
- Buckle 78: subset
- Storage of Galileo broadcast ephemeris in rtklib-b33
- 差点被这波Handler 面试连环炮带走~
- 基于STM32MP157调试MIPI-DSI屏幕
- MySQL简单权限管理
- Knowledge sharing 𞓜 conventional laminated structure of six layer PCB
- CAN总线工作状况和信号质量“体检”
- C Getting Started tutorial
猜你喜欢

基于RBAC 的SAAS系统权限设计

How to select lead-free and lead-free tin spraying for PCB? 2021-11-16

Hisilicon 3559 sample parsing: Vio

Without "rice", you can cook "rice". Strategy for retrieving missing ground points under airborne lidar forest using "point cloud intelligent mapping"

Do you know why the PCB produces tin beads? 2021-09-30

Five causes of PCB board deformation and six solutions 2021-10-08

OpenCV每日函数 结构分析和形状描述符(8) fitLine函数 拟合直线

Pcb|about FPC reinforcement type

新版USBCAN卡CAN分析仪的CAN&CANFD综合测试分析软件LKMaster主要功能介绍

npm install 报错 : gyp ERR! configure error
随机推荐
基于RBAC 的SAAS系统权限设计
Force deduction 76 questions, minimum covering string
2160. 拆分数位后四位数字的最小和
Six causes of PCB disconnection 2021-10-20
Atlassian confluence漏洞分析合集
Insert and sort the linked list [dummy unified operation + broken chain core - passive node]
Application of point cloud intelligent drawing in intelligent construction site
【QT】qtcreator便捷快捷键以及QML介绍
SSL证书免费获取教程
Machine learning notes linear regression of time series
El input to add words to the tail
Import data into Matlab
基于Anaconda的模块安装与注意事项
STL tutorial 4- input / output stream and object serialization
取消word文档中某些页面的页眉
(tool class) use SecureCRT as the communication medium
力扣76题,最小覆盖字串
How to resize an image in C #
國外LEAD域名郵箱獲取途徑
【QT】Qt 5 的程序:打印文档