Linux驱动21 --- FFMPEG 音频 API

发布于:2025-07-28 ⋅ 阅读:(20) ⋅ 点赞:(0)

 目录

 一、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下的channels

in_sample_fmt:输入采样格式

        输入流codec下的sample_fmt

in_sample_rate:输入采样率

        输入流codec下的sample_rate

log_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_samples

in:输入的数组,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参数赋值。‌
	// 512
    Uint16 padding;		对于某些环境需要
    Uint32 size;			size 缓冲区的大小(字节为单位),当我们想要更多声音的时候,我们想让SDL给出来的声音缓冲区的尺寸。一个比较合适的值在512到8192之间;ffplay使用1024
    SDL_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 8
    uint8_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 AVSampleForma
    int 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; // 算法预测的timestamp
    int64_t pkt_pos; // 记录上一个AVPacket输入解码器的位置。
    int64_t pkt_duration; // packet的duration
    AVDictionary *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(); 
    //初始化SDL
    SDL_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;
}

网站公告

今日签到

点亮在社区的每一天
去签到