当前位置:网站首页>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);在循环操作中,使用以上函数进行音频的重采样转换以及输出,可见也简化了输出所需要的代码内容。
边栏推荐
- 挖掘微生物暗物质——新思路
- 2265. number of nodes with statistical value equal to the average value of subtree
- SSL证书免费获取教程
- Atlas conflict Remote Code Execution Vulnerability (cve-2022-26134 vulnerability analysis and protection
- Microsoft Office Word 远程命令执行漏洞(CVE-2022-30190)分析与利用
- Insert and sort the linked list [dummy unified operation + broken chain core - passive node]
- 饮食干预减轻癌症治疗相关症状和毒性
- Linux上oracle和mysql的启动,关闭,重启
- Tips on how to design soft and hard composite boards ~ 22021/11/22
- AttributeError: ‘Upsample‘ object has no attribute ‘recompute_scale_factor‘
猜你喜欢

Anaconda navigator启动慢的一个解决方法

FairMOT yolov5s转onnx

权限、认证系统相关名词概念

Technology blog | how to communicate using SSE

产品经理专业知识50篇(四)-从问题到能力提升:AMDGF模型工具

Modular programming of LCD1602 LCD controlled by single chip microcomputer

使用报文和波形记录分析仪RoyalScope的帧统计功能排查CAN总线偶发性故障

一文了解 | 革兰氏阳性和阴性菌区别,致病差异,针对用药

Take you through the normalization flow of GaN

el-input实现尾部加字
随机推荐
微信小程序开通客服消息功能开发
VOCALOID笔记
Tips on how to design soft and hard composite boards ~ 22021/11/22
【Unexpected token o in JSON at position 1出错原因及解决方法】
力扣76题,最小覆盖字串
C# 读取web上的xml
Advantages and differences of three kinds of vias in PCB 2021-10-27
【日常训练】207. 课程表
一次弄清楚 Handler 可能导致的内存泄漏和解决办法
NPM install reports an error: gyp err! configure error
Manufacturing process of PCB 2021-10-11
Keil and Proteus joint commissioning
将数据导入到MATLAB
微信小程序入门记录
WinForm implementation window is always at the top level
Analysis of kinsing dual platform mining family virus
One "stone" and two "birds", PCA can effectively improve the dilemma of missing some ground points under the airborne lidar forest
[daily training] 207 Class Schedule Card
Technology blog | how to communicate using SSE
[single chip microcomputer project training] multipoint temperature wireless acquisition system based on nRF905