FFmpeg代码备份

该代码示例展示了如何使用FFmpeg库在MacOS上打开音频设备,读取音频数据,以及进行AAC编码。它涉及到avformat_open_input来打开设备,av_read_frame来读取音频帧,以及avcodec_send_frame和avcodec_receive_packet进行音频编码。
摘要由CSDN通过智能技术生成
//
//  TyeFFmpegC.c
//  TyeFFmpegToMacOS
//
//  Created by Black2 on 2023/4/15.
//

#include "TyeFFmpegC.h"

void yeTestDebug(void){
    printf("Tye-C \n");
    av_log_set_level(AV_LOG_DEBUG);
    av_log(NULL, AV_LOG_DEBUG, "Tye 测试 FFmpeg API\n");
    return;
}

///== 6-8打开音频设备(内置的、外接的):
/// #include "libavdevice/avdevice.h"
///#include "libavformat/avformat.h"
AVFormatContext* yeFFmpeg01(void){
    // 1. 注册设备:
    avdevice_register_all();
    
    /// 2. 设置采集方式:avfoundation(MacOS) / dshow(Window)/ alsa(Linux):
    AVInputFormat * eInputFormat = av_find_input_format("avfoundation");
    
    /// 3.打开音频设备:
    AVFormatContext * eAVFormatContext = NULL;
    char * eDeviceName = ":0"; //第一个设备; [[video device]:[audio device]]
    AVDictionary * eDic = NULL;
    int eIntRet = avformat_open_input(&eAVFormatContext, eDeviceName, eInputFormat, &eDic);
    
    char eArrCharError[1024];
    if(eIntRet < 0){ // 出错;
        av_strerror(eIntRet, eArrCharError, 1024);
        fprintf(stderr,"---打开设备,错误信息: %d==%s--\n",eIntRet, eArrCharError);
        return NULL;
    }else{
        printf("---打开设备 成功--\n");
        return &eAVFormatContext;
    }
}


///==6-9从音频设备中读取音频数据: av_read_frame(<#AVFormatContext *s#>, <#AVPacket *pkt#>)
//#include "libavcodec/avcodec.h"
void yeFFmpeg02(void){
    //av_read_frame(<#AVFormatContext *s#>, <#AVPacket *pkt#>) 返回0:表示成功;
    //AVFormatContext: 上下文;
    //AVPacket: 音/视频包; data, size;
    
    //av_init_packet(<#AVPacket *pkt#>) :1.1初始化引用对象;【成对用】
    //av_packet_unref(<#AVPacket *pkt#>) :1.2释放(缓冲区的数据);
    
    //av_packet_alloc() // 2.1开堆内存; 【成对用】
    //av_packet_free(<#AVPacket **pkt#>) //2.2释放堆内存;
    
    
    ///-- 1. 打开音频设备:
    AVFormatContext * eAVFormatContext = yeFFmpeg01();
    if (NULL == eAVFormatContext){ return; }
    
    
    ///-- 2.读取音频数据:
    int eIntCount = 0;//Test;
    int eIntRet = -1;
    AVPacket eAVPacket;
    av_init_packet(&eAVPacket); //初始化;
    while (0 == (eIntRet = av_read_frame(eAVFormatContext, &eAVPacket)) && eIntCount++ < 500) {
        printf("读到的大小及数据:%d==%p", eAVPacket.size, eAVPacket.data); //4096;
        av_packet_unref(&eAVPacket); //释放AVPacket(缓冲区的数据);
    }
    avformat_close_input(&eAVFormatContext); //release上下文, 关闭读取;
    
}


///== 6-11录制音频数据: 创建文件-->写入数据-->关闭文件;
void yeFFmpeg03(void){
    // 1/3 w:写数据; r:读数据; wb:写二进制数据; wb+:创建文件写二进制数据;
    char * eCharPath = "/Users/edy/Desktop/Test/TyeAudio.pcm";
    FILE * eFile = fopen(eCharPath, "wb+");
    
    ///-- 1. 打开音频设备:
    AVFormatContext * eAVFormatContext = yeFFmpeg01();
    if (NULL == eAVFormatContext){ return; }
    
    ///-- 2.读取音频数据:
    int eIntCount = 0;//Test;
    int eIntRet = -1;
    AVPacket eAVPacket;
    av_init_packet(&eAVPacket); //初始化;
    while (0 == (eIntRet = av_read_frame(eAVFormatContext, &eAVPacket)) && eIntCount++ < 500) {
        printf("读到的大小及数据:%d==%p", eAVPacket.size, eAVPacket.data); //4096;
        
        fwrite(eAVPacket.data, eAVPacket.size, 1, eFile); // 2/3 写入数据;
        //fflush(eFile); //直接实时写入磁盘, 效率会降低;(不写入缓冲区)
        
        av_packet_unref(&eAVPacket); //释放AVPacket(缓冲区的数据);
    }
    avformat_close_input(&eAVFormatContext); //release上下文, 关闭读取;
    fclose(eFile); //3/3 关闭文件;
}


///== 8-4创建AAC编码器:
/// AVFrame: 保存未编码的数据;
/// AVPacket: 保存编码后的数据;
AVFrame * yeInitAVFrame(void){
    AVFrame * eAVFrame = NULL;
    eAVFrame = av_frame_alloc(); // 堆上分配内存;
    if(!eAVFrame){
        printf("---Error, No Memory!--\n");
        goto __ERROR;
    }
    
    // 设置参数,set parameters
    eAVFrame->nb_samples     = 512;                //单通道一个音频帧的采样数
    eAVFrame->format         = AV_SAMPLE_FMT_S16;  //每个采样的大小
    eAVFrame->channel_layout = AV_CH_LAYOUT_STEREO; //channel layout
    
    //--- alloc inner memory, 根据上面的三个参数分配buffer的大小:
    av_frame_get_buffer(eAVFrame, 0); // 512 * 2 * = 2048;(即未编码前一帧的大小为2048)
    
    if(!eAVFrame->data[0]){
        printf("---Error, Failed to alloc buf in frame!--\n");
        //内存泄漏
        goto __ERROR;
    }
    return  eAVFrame;
    
    ///--
__ERROR:
    if(eAVFrame){
        av_frame_free(&eAVFrame);
    }
    return NULL;
}

AVPacket * yeInitAVPacket(void){
    AVPacket * eAVPacket = NULL;
    eAVPacket = av_packet_alloc(); // 堆上分配内存;
    if(!eAVPacket){
        //...
    }
    return  eAVPacket;
}


/ 打开编码器:
AVCodecContext* yeInitAVCodecContext(void){
    // 1. 创建编码器: (两种方式)
    const AVCodec * eAVCodec = NULL;
    //eAVCodec = avcodec_find_encoder(AV_CODEC_ID_OPUS);
    //eAVCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    eAVCodec = avcodec_find_encoder_by_name("libfdk_aac");
    
    // 2.创建 codec 上下文:
    AVCodecContext * eAVCodecContext = NULL;
    eAVCodecContext = avcodec_alloc_context3(eAVCodec);
    
    // 3. 设置参数:
    eAVCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;          //输入音频的采样大小
    eAVCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;    //输入音频的channel layout
    eAVCodecContext->channels = 2;                            //输入音频 channel 个数
    eAVCodecContext->sample_rate = 44100;                     //输入音频的采样率
    //eAVCodecContext->bit_rate = 64000; //码率的大小, AAC_LC: 128K, AAC HE: 64K, AAC HE V2: 32K;
    eAVCodecContext->bit_rate = 0;// 根据profile文件自动设置;
    eAVCodecContext->profile = FF_PROFILE_AAC_HE_V2; //bit_rate=0时才有效;
    
    // 4. 打开编码器:
    int eIntRet = -1;
    eIntRet = avcodec_open2(eAVCodecContext, eAVCodec, NULL);
    if(eIntRet < 0){
        printf("yeInitAVCodecContext 出错");
    }
    
    return eAVCodecContext;
}

///#include "libswresample/swresample.h"
SwrContext* yeInitSwrContext(void){ //初始化采样上下文;
    SwrContext *eSwrContext = NULL; //重采样的上下文;
    eSwrContext = swr_alloc_set_opts(NULL,            //上下文;
                                 AV_CH_LAYOUT_STEREO, //输出channel布局
                                 AV_SAMPLE_FMT_S16,   //输出的采样格式
                                 44100,               //采样率
                                 AV_CH_LAYOUT_STEREO, //输入channel布局
                                 AV_SAMPLE_FMT_FLT,   //输入的采样格式
                                 44100,               //输入的采样率
                                 0, NULL);
    if(!eSwrContext){
        //...
    }
    
    //  初始化:
    int eIntRet0 = swr_init(eSwrContext);
    if(eIntRet0 < 0){
        printf("---初始化采样上下文 出错--");
    }
    return eSwrContext;
}

void yeFFmpeg04(void){
    /// w:写数据; r:读数据; wb:写二进制数据; wb+:创建文件写二进制数据;
    char * eCharPath = "/Users/edy/Desktop/Test/TyeAudio.pcm";
    FILE * eFile = fopen(eCharPath, "wb+");
    
    // 打开编码器:
    AVCodecContext * eAVCodecContext = yeInitAVCodecContext();
    AVFrame * eAVFrame = yeInitAVFrame();
    
    //AVPacket * eAVPacket = av_packet_alloc();
    
    
    
    //
    //采样上下文:
    SwrContext * eSwrContext = yeInitSwrContext();
    
    ///-- 1. 打开音频设备:
    AVFormatContext * eAVFormatContext = yeFFmpeg01();
    if (NULL == eAVFormatContext){ return; }
    
    ///-- 2.读取音频数据:
    int eIntCount = 0;//Test;
    int eIntRet = -1;
    AVPacket eAVPacket;
    av_init_packet(&eAVPacket); //初始化;
    
    //
    uint8_t **src_data = NULL;
    int src_linesize = 0;
    //4096/4=1024/2=512
    //创建输入缓冲区
    av_samples_alloc_array_and_samples(&src_data,         //输出缓冲区地址
                                       &src_linesize,     //缓冲区的大小
                                       2,                 //通道个数
                                       512,               //单通道采样个数
                                       AV_SAMPLE_FMT_FLT, //采样格式
                                       0);
    
    //创建输出缓冲区
    uint8_t **dst_data = NULL;
    int dst_linesize = 0;
    av_samples_alloc_array_and_samples(&dst_data,         //输出缓冲区地址
                                       &dst_linesize,     //缓冲区的大小
                                       2,                 //通道个数
                                       512,               //单通道采样个数
                                       AV_SAMPLE_FMT_S16, //采样格式
                                       0);
    
    
    while (0 == (eIntRet = av_read_frame(eAVFormatContext, &eAVPacket)) && eIntCount++ < 500) {
        printf("读到的大小及数据:%d==%p", eAVPacket.size, eAVPacket.data); //4096;
        
        //将重采样的数据拷贝到src_data[]中:
        memcpy((void*)src_data[0], (void*)eAVPacket.data, eAVPacket.size);
        
        /// 重采样:
        swr_convert(eSwrContext,                //重采样的上下文
                    dst_data,                   //输出结果缓冲区
                    512,                        //每个通道的采样数
                    (const uint8_t **)src_data, //输入缓冲区
                    512);
        
        fwrite(dst_data[0], 1, dst_linesize, eFile);
        av_packet_unref(&eAVPacket); //释放AVPacket(缓冲区的数据);
    }
    //
    fclose(eFile);
    
    //
    if(src_data){
        av_freep(&src_data[0]);
    }
    av_freep(src_data);
    
    if(dst_data){
        av_freep(&dst_data[0]);
    }
    av_freep(dst_data);
    
    //
    swr_free(&eSwrContext);
    
    //
    avformat_close_input(&eAVFormatContext); //release上下文, 关闭读取;
}


///== 8-6 AAC编码,输入输出数据:
///
///
void yeFFmpegToEncode(AVCodecContext * eAVCodecContext, AVFrame * eAVFrame, AVPacket * eAVPacket, FILE * eFile){
    /// 把一帧数据输入给编码器:
    int eIntFrameR = -1;
    eIntFrameR = avcodec_send_frame(eAVCodecContext, eAVFrame);
    
    /// 进行编码:
    while (eIntFrameR > 0) { // 因为有缓存,有可能没有返回数据,也可能返回多个数据;所以用循环获取;
        eIntFrameR = avcodec_receive_packet(eAVCodecContext, eAVPacket);
        //if(eIntFrameR >= 0) // 获取成功; 继续循环获取剩余的数据;
        
        if(eIntFrameR < 0){ //没有编码好的数据了;
            if(eIntFrameR == AVERROR(EAGAIN) || eIntFrameR == AVERROR_EOF){
                //没有编码好的数据了;
                break;
            }else{
                printf("---编码器出错了, 退出程序--");
            }
        }
        
        //--- 写入文件:
        fwrite(eAVPacket->data, 1, eAVPacket->size, eFile);
        fflush(eFile);
    }
}
    
    

///    ///-- 重要API:
// 将一帧一帧的数据输入给编码器: 输入的是 Frame : AVFrame(未编码的数据);
//avcodec_send_frame(<#AVCodecContext *avctx#>, <#const AVFrame *frame#>)


// 收到数据后进行编码: 编码得到 Packet : AVPacket(编码后的数据);
//avcodec_receive_packet(<#AVCodecContext *avctx#>, <#AVPacket *avpkt#>)
void yeFFmpeg05(void){
    ///-- 1. w:写数据; r:读数据; wb:写二进制数据; wb+:创建文件写二进制数据;
    char * eCharPath = "/Users/edy/Desktop/Test/TyeAudio.pcm";
    FILE * eFile = fopen(eCharPath, "wb+");
    
    ///-- 2. 打开编码器:
    AVCodecContext * eAVCodecContext = yeInitAVCodecContext();
    
    //--
    AVFrame * eAVFrame = yeInitAVFrame();
    AVPacket * eAVPacket = yeInitAVPacket();
//    av_init_packet(&eAVPacket); //初始化;
    
    ///-- 3.采样上下文:
    SwrContext * eSwrContext = yeInitSwrContext();
    
    ///-- 4. 打开音频设备:
    AVFormatContext * eAVFormatContext = yeFFmpeg01();
    if (NULL == eAVFormatContext){ return; }
    
    ///-- 5.读取音频数据:
    int eIntCount = 0;//Test;
    int eIntRet = -1;
    
    ///-- 6.
    uint8_t **src_data = NULL;
    int src_linesize = 0;
    //4096/4=1024/2=512
    //创建输入缓冲区
    av_samples_alloc_array_and_samples(&src_data,         //输出缓冲区地址
                                       &src_linesize,     //缓冲区的大小
                                       2,                 //通道个数
                                       512,               //单通道采样个数
                                       AV_SAMPLE_FMT_FLT, //采样格式
                                       0);
    
    //创建输出缓冲区
    uint8_t **dst_data = NULL;
    int dst_linesize = 0;
    av_samples_alloc_array_and_samples(&dst_data,         //输出缓冲区地址
                                       &dst_linesize,     //缓冲区的大小
                                       2,                 //通道个数
                                       512,               //单通道采样个数
                                       AV_SAMPLE_FMT_S16, //采样格式
                                       0);
    
    
    ///-- 7.
    while (0 == (eIntRet = av_read_frame(eAVFormatContext, &eAVPacket)) && eIntCount++ < 500) {
        printf("读到的大小及数据:%d==%p", eAVPacket->size, eAVPacket->data); //4096;
        
        //将重采样的数据拷贝到src_data[]中:
        memcpy((void*)src_data[0], (void*)eAVPacket->data, eAVPacket->size);
        
        /// 重采样:
        swr_convert(eSwrContext,                //重采样的上下文
                    dst_data,                   //输出结果缓冲区
                    512,                        //每个通道的采样数
                    (const uint8_t **)src_data, //输入缓冲区
                    512);
        
        /// 将重采样的数据复制到 AVFrame中:
        memcpy((void*)eAVFrame->data[0], (void*)dst_data[0], dst_linesize);
        yeFFmpegToEncode(eAVCodecContext, eAVFrame, eAVPacket, eFile); //编码;
        
        av_packet_unref(&eAVPacket);
    }
    
    ///-- 输入eAVFrame == NULL:告诉编码器没有数据再输入了;
    yeFFmpegToEncode(eAVCodecContext, NULL, eAVPacket, eFile); //编码;
    
    
    ///-- 8.
    fclose(eFile);
    
    if(src_data){
        av_freep(&src_data[0]);
    }
    av_freep(src_data);
    
    if(dst_data){
        av_freep(&dst_data[0]);
    }
    av_freep(dst_data);
    swr_free(&eSwrContext);
    avformat_close_input(&eAVFormatContext); //release上下文, 关闭读取;
}




void yeFFmpeg00(void){
    
}


void yeTestFFmpeg(int eInt){
    yeFFmpeg02();
    return;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值