当前位置: 首页 > news >正文

Linux驱动21 --- FFMPEG 音频 API

 目录

 一、FFMPEG 音频 API

1.1 解码步骤

        创建核心上下文指针

        打开输入流

        获取输入流

        获取解码器

        初始化解码器

        创建输入流指针

        创建输出流指针

        初始化 SDL

        配置音频参数

        打开音频设备

        获取一帧数据

        发送给解码器

        从解码器获取数据

        开辟数据空间

        初始化内存        

        音频重采样配置 --- 相当于视频的格式转换

        由通道数获取默认的通道布局

        初始化重采样核心结构体

        音频重采样

        播放

        延时

1.2 参数扩展

        SDL_AudioSpec

        AVFrame

二、FFMPEG 录制声音的过程

2.1 步骤

        FFMPEG 注册所有  

        FFMPEG 核心上下文申请

        查找音频设备

        注册音频设备

        SDL 初始化

        配置音频参数

        打开音频设备

        读取一帧数据

        写入到文件

三、如何在板子上实现


一、FFMPEG 音频 API

1.1 解码步骤

        创建核心上下文指针

                AVFormatContext * avfmtctx  = avformat_alloc_context();

        打开输入流

                avformat_open_input(&avfmtctx, argv[1], NULL, NULL);

        获取输入流

                avformat_find_stream_info(avfmtctx, NULL);

        获取解码器

                AVCodecContext * avcodectx = avfmtctx->streams[0]->codec;
                AVCodec *avcodec = avcodec_find_decoder(avcodectx->codec_id);

        初始化解码器

                avcodec_open2(avcodectx, avcodec, NULL); 

        创建输入流指针

                AVPacket * avpkt = av_packet_alloc();

        创建输出流指针

                AVFrame * avfrm = av_frame_alloc(); 

        初始化 SDL

        函数头文件

                #include <SDL2/SDL.h>

        函数原型

                int SDL_Init(Uint32 flags)

        函数参数

                常用参数

                        SDL_INIT_TIMER

                        SDL_INIT_AUDIO

                        SDL_INIT_VIDEO

        函数返回值

                成功时返回0,失败时返回负数错误码; 调用SDL_GetError()可以获得本次异常信息。

        配置音频参数

        函数原型

                int SDL_OpenAudioDevice(const char  *device,int iscapture,const SDL_AudioSpec *desired, SDL_AudioSpec *obtained,int allowed_changes);  

        函数参数

                device:音频设备的名称,NULL表示使用默认设备

                iscapture:设为0,非0的值在当前SDL2版本还不支持

                desired:期望得到的音频输出格式

                obtained:实际的输出格式

                allowed_changes:该参数用来指定 当期望和实际的不一样时,能不能够对某一些输出参数进行修改。 设为0,则不能修改。设为如下的值,则可对相应的参数修改:

                        SDL_AUDIO_ALLOW_FREQUENCY_CHANGE

                        SDL_AUDIO_ALLOW_FORMAT_CHANGE

                        SDL_AUDIO_ALLOW_CHANNELS_CHANGE

                        SDL_AUDIO_ALLOW_ANY_CHANGE

        函数返回值

                0失败;成功返回有效音频设备号 >= 2

        打开音频设备

        函数原型

                void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev, int pause_on)

        函数参数

                dev:SDL_OpenAudioDevice函数返回值

                pause_on:根据介绍,填0即可

        av_read_frame

        avcodec_send_packet

        avcodec_receive_frame

        获取一帧数据

                av_read_frame(avfmtctx, avpkt)

        发送给解码器

                avcodec_send_packet(avcodectx, avpkt);

        从解码器获取数据

                avcodec_receive_frame(avcodectx, avfrm);

        开辟数据空间

        函数原型

                int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align)

        函数参数

                linesize:计算的lineize,可能为NULL

                nb_channels:声道数

                SDL_AudioSpec结构体中channels成员变量

                nb_samples:单个通道中的样本数

                        采样频率(Hz) *当前帧的音频采样数/当前帧的音频数据的采样率

                sample_fmt:样本格式

                align:对齐缓冲区大小对齐(0 =默认,1 =无对齐)

        函数返回值

                需要的缓冲区大小,或失败时出现负错误代码

        初始化内存        

                调用av_malloc,然后再将内存内容清零

        函数原型

                void *av_mallocz(size_t size)

                申请数据存放空间

        音频重采样配置 --- 相当于视频的格式转换

        根据输入和输出参数,并设置相关选项

        函数头文件

                #include "libswresample/swresample.h"

        函数原型

        SwrContext *swr_alloc_set_opts(SwrContext *s, int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate, int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate, int log_offset, void *log_ctx)

        函数参数

s:可选的现有SwrContext,如果不为NULL,则会使用该现有上下文。out_ch_layout:输出声道布局(Channel Layout)通过函数av_get_default_channel_layout获取根据SDL_AudioSpec的channels获取out_sample_fmt:输出采样格式根据SDL_AudioSpec的format成员选取out_sample_rate:输出采样率SDL_AudioSpec的freq成员in_ch_layout:输入声道布局通过函数av_get_default_channel_layout获取输入流codecpar下的channelsin_sample_fmt:输入采样格式输入流codec下的sample_fmtin_sample_rate:输入采样率输入流codec下的sample_ratelog_offset:日志偏移量,填0即可log_ctx:日志上下文,填NULL即可
        由通道数获取默认的通道布局

        函数原型

                int64_t av_get_default_channel_layout(int nb_channels);    

        初始化重采样核心结构体

        函数原型

                int swr_init(struct SwrContext *s);

        函数参数

                s:swr_alloc_set_opts返回值

        音频重采样

                针对每一帧音频的处理。把一帧帧的音频作相应的重采样

        函数原型

                int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count);

s:音频重采样的上下文out:输出的指针。传递的输出的数组out_count:输出的样本数量,不是字节数。单通道的样本数量。av_samples_get_buffer_size参数nb_samplesin:输入的数组,AVFrame解码出来的DATA(成员extended_data)in_count:输入的单通道的样本数量AVFrame结构体的nb_samples成员

        函数返回值

                每个通道输出的样本数,失败为负值

        播放

        函数功能

                使用此函数可以在回调设备(即使用了第一套API需要回调函数填充数据的设备)上缓存更多音频,而不必通过回调函数填充音频数据。

        函数原型

                int SDL_QueueAudio(SDL_AudioDeviceID dev, const void* data, Uint32 len)

        函数参数

                dev:设备ID

                data:需要被填充的数据指针

                len:数据buffer长度,byte为单位

                        通过函数av_samples_get_buffer_size获取

        函数返回值

                0表示成功,非零表示出现异常

        //av_samples_get_buffer_size

        延时

        SDL_Delay

        SDL_Delay((输出数据大小) * 1000.0 / (音频采样率 * av_get_bytes_per_sample(音频格式) * 通道数量) - 1);

1.2 参数扩展

        SDL_AudioSpec

int freq;		freq 每秒钟发送给音频设备的sample frame的个数,通常是11025,220502,44100和48000。(sample frame = 样本精度 * 通道数)//输入流codec中sample_rate成员
SDL_AudioFormat format;		fromat 每个样本占用的空间大小及格式,例如 AUDIO_S16SYS,样本是有符号的16位整数,字节顺序(大端还是小端)和系统一样。更多的格式可参考SDL_AudioFormat。// AUDIO_F32SYS
Uint8 channels; 		channels 通道数,在SDL2.0中支持1(mono),2(stereo),4(quad)和6(5.1)//输入流codecpar中channels成员
Uint8 silence;			silence 音频数据中表示静音的值是多少//填0即可
Uint16 samples;		这是每次读取的采样数量,‌决定了音频数据回调的频率。‌例如,‌设置为1024时,‌表示每次读取1024个样本数据,‌回调函数被调用一次。‌这个值不一定是2的幂指数次方,‌最好由AVFrame->nb_samples参数赋值。‌// 512Uint16 padding;		对于某些环境需要Uint32 size;			size 缓冲区的大小(字节为单位),当我们想要更多声音的时候,我们想让SDL给出来的声音缓冲区的尺寸。一个比较合适的值在512到8192之间;ffplay使用1024SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */		callback用来音频设备缓冲区的回调函数
void *userdata;		userdata在回调函数中使用的数据指针

        AVFrame

typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8uint8_t *data[AV_NUM_DATA_POINTERS]; // 存放媒体数据的指针数组int linesize[AV_NUM_DATA_POINTERS]; // 视频或音频帧数据的行宽uint8_t **extended_data; // 音频或视频数据的指针数组。int width, height; // 视频帧的款和高/*** number of audio samples (per channel) described by this frame*/int nb_samples; // 当前帧的音频采样数(每个通道)int format; // 视频帧的像素格式,见enum AVPixelFormat,或音频的采样格式,见enum AVSampleFormaint key_frame; // 当前帧是否为关键帧,1表示是,0表示不是。AVRational sample_aspect_ratio; // 视频帧的样本宽高比int64_t pts; // 以time_base为单位的呈现时间戳(应向用户显示帧的时间)。int64_t pkt_dts; // 从AVPacket复制而来的dts时间,当没有pts时间是,pkt_dts可以替代pts。int coded_picture_number; // 按解码先后排序的,解码图像数int display_picture_number; // 按显示前后排序的,显示图像数。int quality; // 帧质量,从1~FF_LAMBDA_MAX之间取之,1表示最好,FF_LAMBDA_MAX之间取之表示最坏。void *opaque; // user的私有数据。int interlaced_frame; // 图片的内容是隔行扫描的(交错帧)。int top_field_first; // 如果内容是隔行扫描的,则首先显示顶部字段。int sample_rate; // 音频数据的采样率uint64_t channel_layout; // 音频数据的通道布局。/*** AVBuffer引用,当前帧数据。 如果所有的元素为NULL,则此帧不是引用计数。 必须连续填充此数组,* 即如果buf [i]为非NULL,j <i,buf[j]也必须为非NULL。** 每个数据平面最多可以有一个AVBuffer,因此对于视频,此数组始终包含所有引用。 * 对于具有多于AV_NUM_DATA_POINTERS个通道的平面音频,可能有多个缓冲区可以容纳在此阵列中。 * 然后额外的AVBufferRef指针存储在extended_buf数组中。*/AVBufferRef *buf[AV_NUM_DATA_POINTERS];AVBufferRef **extended_buf; // AVBufferRef的指针int        nb_extended_buf; // extended_buf的数量enum AVColorSpace colorspace; // YUV颜色空间类型。int64_t best_effort_timestamp; // 算法预测的timestampint64_t pkt_pos; // 记录上一个AVPacket输入解码器的位置。int64_t pkt_duration; // packet的durationAVDictionary *metadata;int channels; // 音频的通道数。int pkt_size; // 包含压缩帧的相应数据包的大小。} AVFrame;

二、FFMPEG 录制声音的过程

2.1 步骤

        FFMPEG 注册所有  

        头文件

                #include "libavdevice/avdevice.h"
        函数原型

                void avdevice_register_all(void)

        FFMPEG 核心上下文申请

                AVFormatContext * avfmtctx  = avformat_alloc_context();

        查找音频设备

        函数原型

                AVInputFormat *av_find_input_format(const char *short_name)

        函数参数

                直接填alsa即可

        函数返回值

                就是需要的输入设备的核心上下文指针

        注册音频设备

        函数原型

                int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options)

        函数参数

                ps:FFMPEG的核心上下文指针

                url:在此需要使用声卡的名字

                "plughw:CARD=AudioPCI,DEV=0"

                fmt:av_find_input_format函数返回值

                options:填NULL

        SDL 初始化

                SDL_Init(SDL_INIT_AUDIO);

        配置音频参数

        函数原型

                int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)

                        desired:想要的配置

                        obtained:实际得到的配置,此处填NULL即可

        打开音频设备

                void SDL_PauseAudio(int pause_on)

        读取一帧数据

                int av_read_frame(AVFormatContext *s, AVPacket *pkt)

        写入到文件

                fwrite

三、如何在板子上实现

1、在 buildroot 中勾选 ffmpeg 选项、SDL 选项

2、编译文件系统 --- 生成新的文件系统

3、烧录新的文件系统

4、和之前 LVGL 相同 --- 修改 Makefile

        4.1 CC 换成 buildroot 的交叉编译工具

        4.2 把之前该删除的依赖库删除,把需要的库给加上

5、编译

        可能出现的问题

        buildroot 支持的 ffmpeg 和 SDL 版本和程序中使用的版本不符

6、运行

代码

dec_audio.c //音频

#include <stdio.h>
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include <SDL2/SDL.h>int main(int argc, char *argv[])
{if(argc < 2){printf("./play <name>\n");return 0;}//创建核心上下文指针AVFormatContext * avfmtctx  = avformat_alloc_context();//打开输入流avformat_open_input(&avfmtctx, argv[1], NULL, NULL);//获取输入流avformat_find_stream_info(avfmtctx, NULL); //到此会获取//输入流获取之后获取的是纯音频文件,只有一个流 ,所以还用 avfmt->streams[0]//有的音频,会带一个图片封面 --- 这种音频会报段错误//获取解码器AVCodecContext * avcodectx = avfmtctx->streams[0]->codec;AVCodec *avcodec = avcodec_find_decoder(avcodectx->codec_id);//初始化解码器avcodec_open2(avcodectx, avcodec, NULL); //创建输入流指针AVPacket * avpkt = av_packet_alloc();   //存放输入流中一帧图像//创建输出流指针AVFrame * avfrm = av_frame_alloc(); //初始化SDLSDL_Init(SDL_INIT_AUDIO);//配置音频参数SDL_AudioSpec desired, obtained;    //一个期望的,一个获得的desired.callback = NULL;desired.channels = 2;    //期望的通道数desired.format = AUDIO_S16SYS;  //音频的格式desired.freq = avcodectx->sample_rate;    //采样率 --- 需要注意desired.padding = 0;desired.samples = 1152; //采样数desired.silence = 0;desired.size = 0;       //desired.userdata = NULL;//打开音频设备SDL_AudioDeviceID aid = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE);  //理论上ID会大于0//启动音频设备SDLCALL SDL_PauseAudioDevice(aid, 0);enum AVSampleFormat mysfmt;switch(obtained.format){case AUDIO_S16SYS: mysfmt = AV_SAMPLE_FMT_S16; break;case AUDIO_S32SYS: mysfmt = AV_SAMPLE_FMT_S32; break;case AUDIO_F32SYS: mysfmt = AV_SAMPLE_FMT_FLT; break;}int size;uint8_t *data = NULL;while(1){//获取一帧数据if(av_read_frame(avfmtctx, avpkt) != 0){printf("获取文件错误/到达文件结尾\n");break;}//发送给解码器avcodec_send_packet(avcodectx, avpkt);//从解码器获取数据avcodec_receive_frame(avcodectx, avfrm);    //第一帧和第二帧获取的大小不一样 --- 把开辟空间方在里面//开辟数据空间size = av_samples_get_buffer_size(NULL, obtained.channels, avfrm->nb_samples, mysfmt, 0);//初始化内存data = av_mallocz(size);//音频重采样配置 --- 相当于视频的格式转换struct SwrContext * swrctx = swr_alloc_set_opts(NULL, av_get_default_channel_layout(obtained.channels), mysfmt, obtained.freq, \av_get_default_channel_layout(avcodectx->channels), avcodectx->sample_fmt, avcodectx->sample_rate, 0, NULL);//初始化重采样核心结构体swr_init(swrctx);//音频重采样swr_convert(swrctx, &data, size, avfrm->extended_data, avfrm->nb_samples);//播放SDL_QueueAudio(aid, data, size);//延时//SDL_Delay((输出数据大小) * 1000.0 / (音频采样率 * av_get_bytes_per_sample(音频格式) * 通道数量) - 1);SDL_Delay((size) * 1000.0 / (obtained.freq * av_get_bytes_per_sample(mysfmt) * obtained.channels) - 1);    //网上有两种说法,1.当前的音频播放需要时间 2.音频帧不够,通过延时,补全av_free(data);}
}

get_audio.c //录音

#include <stdio.h>
#include "libavformat/avformat.h"
#include <SDL2/SDL.h>
#include <pthread.h>
#include <unistd.h>
#include "libavdevice/avdevice.h"int end_flag = 0;void *pthread_count_func(void *arg)
{int num = 0;while(num--){sleep(1);printf("录音剩余 %d 秒\n", num);}end_flag = 1;
}int main(void)
{//FFMPEG注册所有 --- 必须写avdevice_register_all();//FFMPEG核心上下文申请AVFormatContext * avfmtctx  = avformat_alloc_context();//查找音频设备AVInputFormat * avifmt = av_find_input_format("alsa");//注册音频设备avformat_open_input(&avfmtctx, "hw:CARD=AudioPCI,DEV=0", avifmt, NULL);//SDL初始化SDL_Init(SDL_INIT_AUDIO);//配置音频参数 --- 再此配置为期望的,得到的填NULL即可SDL_AudioSpec desired;    //一个期望的,一个获得的desired.callback = NULL;desired.channels = 2;    //期望的通道数desired.format = AUDIO_S16SYS;  //音频的格式desired.freq = 48000;    //采样率 --- 过低声音会很奇怪desired.padding = 0;desired.samples = 1152; //采样数desired.silence = 0;desired.size = 0;       //desired.userdata = NULL;SDL_OpenAudio(&desired, NULL);//打开音频设备SDL_PauseAudio(0);AVPacket * avpkt = av_packet_alloc();   //存放输入流中一帧数据FILE *file = fopen("./9203.pcm", "w");pthread_t pd = 0;pthread_create(&pd, NULL, pthread_count_func, NULL);while(1){if(end_flag){break;}//读取一帧数据av_read_frame(avfmtctx, avpkt);//写入到文件fwrite(avpkt->data, 1, avpkt->size, file);}fclose(file);return 0;
}
http://www.xdnf.cn/news/1198387.html

相关文章:

  • 深度解析 inaSpeechSegmenter:高效音频语音分割与检测开源工具
  • STL——list
  • Web Worker:解锁浏览器多线程,提升前端性能与体验
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博文章数据可视化分析-文章分类下拉框实现
  • PHP框架之Laravel框架教程:3. 数据库操作(简要)
  • Keil MDK 嵌入式开发问题:warning: #223-D: function “sprintf“ declared implicitly
  • Flutter开发实战之测试驱动开发
  • IP--MGER综合实验报告
  • 人工智能——图像梯度处理、边缘检测、绘制图像轮廓、凸包特征检测
  • 【MySQL篇】:MySQL基础了解以及库和表的相关操作
  • 2.苹果ios逆向-Windows电脑端环境搭建-Conda安装和使用(使用Conda来管理多个Python环境)
  • LeetCode第350题_两个数组的交集II
  • 图像处理:第二篇 —— 选择镜头的基础知识及对图像处理的影响
  • 代码随想录算法训练营二十八天|动态规划part01
  • ArkTS 模块通信全解析:用事件总线实现页面消息联动
  • LeetCode第349题_两个数组的交集
  • 【LeetCode】LRU 缓存 题解
  • MySQL 全详解:从入门到精通的实战指南
  • LeetCode 刷题【16. 最接近的三数之和、17. 电话号码的字母组合】
  • 【前端】【vscode】【.vscode/settings.json】为单个项目配置自动格式化和开发环境
  • 关系与逻辑运算 —— 寄存器操作的 “入门钥匙”
  • 分布式系统中Token续期问题解决方案
  • AIC 2025 热点解读:如何构建 AI 时代的“视频神经中枢”?
  • 四、搭建springCloudAlibaba2021.1版本分布式微服务-加入openFeign远程调用和sentinel流量控制
  • 嵌入式——单片机的独立按键
  • git stash 命令详解
  • leetcode_560 和为K的子数组
  • C语言——————学习笔记(自己看)
  • 2025.7.27总结—新励成
  • Leetcode 3629. Minimum Jumps to Reach End via Prime Teleportation