zoukankan      html  css  js  c++  java
  • FFmpeg编程(二)FFmpeg中级开发

    一:H264解码处理

    (一)解码步骤

    1.引入解码头文件

    #include <libavcodec/avcodec.h>

    2.常用数据结构

    AVCodec编码器结构体:          所使用的编码器类型,(H264/H265,音频/视频)
    AVCodecContext编码器上下文: 串联各个API,形成API链条,每个API都需要我们把上下文作为参数传入该API。内部也保存了编解码器信息,可以供其调用
    AVFrame解码后的帧: 未压缩的帧(未编码)

    3.结构体内存的分配和释放

    4.解码步骤

    avcodec_find_decoder通过id查找解码器,当然也可以通过名字by_name查找到编解码器(如下面的编码);两种方式各有好处
    avcodec_decode_video2编解码一般使用外部编解码库,比如libx264;

    注意:avcodec_decode_video2与后面的avcodec_decode_audio4函数解码:是指从packet中解析出来一帧一帧数据,并不涉及数据格式的转换,如果要进行格式的转换,需要设置重采样方法等等!!

    (二)编程实战(YUV视频流转RGB图像)

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <libavutil/log.h>
    #include <libavformat/avformat.h>
    #include <libavcodec/avcodec.h>
    #include <libswscale/swscale.h>    //ibswscale是一个主要用于处理图片像素数据的类库。可以完成图片像素格式的转换,图片的拉伸等工作
    
    #define WORD uint16_t
    #define DWORD uint32_t
    #define LONG int32_t
    
    //https://www.cnblogs.com/lzlsky/archive/2012/08/16/2641698.html
    typedef struct tagBITMAPFILEHEADER {
      WORD  bfType;            //位图类别,根据不同的操作,系统而不同,在Windows中,此字段的值总为‘BM’
      DWORD bfSize;            //BMP图像文件的大小
      WORD  bfReserved1;    //保留,为0
      WORD  bfReserved2;    //保留,为0
      DWORD bfOffBits;        //BMP图像数据的地址
    } BITMAPFILEHEADER, *PBITMAPFILEHEADER;
    
    typedef struct tagBITMAPINFOHEADER {
      DWORD biSize;            //包含的是这个结构体的大小(包括颜色表)
      LONG  biWidth;        //是图片的长
      LONG  biHeight;        //是图片的宽
      WORD  biPlanes;        //是目标绘图设备包含的层数,必须设置为1
      WORD  biBitCount;        //是图像的位数,例如24位,8位等
      DWORD biCompression;    //压缩方式,0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定
      DWORD biSizeImage;    //BMP图像数据大小,必须是4的倍数,图像数据大小不是4的倍数时用0填充补足
      LONG  biXPelsPerMeter;//水平分辨率,单位像素/m
      LONG  biYPelsPerMeter;//垂直分辨率,单位像素/m
      DWORD biClrUsed;        //BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为100h=256
      DWORD biClrImportant;    //重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色
    } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
    
    void saveBMP(struct SwsContext* img_convert_cxt,AVFrame* frame,char* filename){
        //1.先进行转换,YUV420=>RGB24
        int w = frame->width;
        int h = frame->height;
    
        int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24,w,h);    //计算字节数量
        uint8_t* buffer = (uint8_t*)av_malloc(numBytes*sizeof(uint8_t));    //分配空间
    
        AVFrame* pFrameRGB = av_frame_alloc();
        //avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据。
        avpicture_fill((AVPicture*)pFrameRGB,buffer,AV_PIX_FMT_RGB24,w,h);
        //真正用来做转换的函数:https://www.cnblogs.com/yongdaimi/p/10715830.html
        sws_scale(img_convert_cxt,frame->data,frame->linesize,    //当前处理区域的每个通道数据指针,每个通道行字节数
            0,h,    //参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。
            pFrameRGB->data,pFrameRGB->linesize);    //定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)
    
        //上面将数据转换完成,下面初始化结构体,写入BMP图片数据,到文件中去
        //---构造BITMAPINFOHEADER信息首部(第二部分,先有文件头,再有信息头,再之后是数据)
        BITMAPINFOHEADER header;
    
        header.biSize = sizeof(BITMAPINFOHEADER);
        header.biWidth = w;
        /*
        如果该值是一个正数,说明Btimap是Bottom up DIB,起始点是左下角,也就是从图像的最下面一行扫描,位图数组中得到的第一行数据实际是图形的最下面的一行。图像是倒向的;
        如果该值是一个负数,则说明图像是TopDown DIB,起始点是左上角,图像从最上面一行扫描,图像正向的。
        大多数的BMP文件都是倒向的位图,也就是时,高度值是一个正数。(注:当高度值是一个负数时(正向图像),图像将不能被压缩(也就是说biCompression成员将不能是BI_RLE8或BI_RLE4)
        */
        header.biHeight = h*(-1);            //biHeight字段的正负号指定DIB图像的绘制方向,负数表示为正向,不被压缩
        header.biPlanes = 1;                //必须为1
        header.biBitCount = 24;                //RBG位深24
        header.biCompression = 0;            //不压缩
        header.biSizeImage = 0;                //其中 biSizeImage 如果不为 0 这代表位图中实际的像素数据字节数;同时如果为0,位图像素数据的字节数也可以通过 biWidth biHeight biBitCount 计算得到。
        header.biXPelsPerMeter = 0;            //设置分辨率 像素/米
        header.biYPelsPerMeter = 0;    
        header.biClrUsed = 0;                //使用全部颜色
        header.biClrImportant = 0;            //全都重要
    
        //---BITMAPFILEHEADER文件头(第一部分)
        BITMAPFILEHEADER bmpFileHeader;
        bmpFileHeader.bfType = 0x4d42;    //"BM"
        bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+numBytes;
        bmpFileHeader.bfReserved1 = 0;
        bmpFileHeader.bfReserved2 = 0;
        bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
    
        FILE* fp = fopen(filename,"wb");
        //由于linux上4字节对齐,而信息头大小为54字节,第一部分14字节,第二部分40字节,所以会将第一部分补齐为16自己,直接用sizeof,打开图片时就会遇到premature end-of-file encountered错误  
        //下面的方式可以防止对齐,避免对齐导致出错
        fwrite(&bmpFileHeader,8,1,fp);    //先把bfType、bfSize、bfReserved1写入
        fwrite(&bmpFileHeader.bfReserved2,sizeof(bmpFileHeader.bfReserved2),1,fp);
        fwrite(&bmpFileHeader.bfOffBits,sizeof(bmpFileHeader.bfOffBits),1,fp);
    
        fwrite(&header,sizeof(BITMAPINFOHEADER),1,fp);
        
        //-----------进行色彩矫正,RGB-->GBR才能变为原本的色彩
        for(int i=0;i<numBytes-3;i+=3){
            uint8_t temp = pFrameRGB->data[0][i];
            pFrameRGB->data[0][i] = pFrameRGB->data[0][i+1];
            pFrameRGB->data[0][i+1] = temp;
    
            temp = pFrameRGB->data[0][i+1];
            pFrameRGB->data[0][i+1] = pFrameRGB->data[0][i+2];
            pFrameRGB->data[0][i+2] = temp;
        }
    
        fwrite(pFrameRGB->data[0],1,numBytes,fp);    //对于RGB只需要一个数组,YUV需要3个
        fclose(fp);
    
        //释放资源
        av_freep(&pFrameRGB[0]);
        av_freep(pFrameRGB);
    }
    
    int decode_write_frame(const char* out_filename,AVCodecContext* avctx,struct SwsContext* img_convert_cxt,
                            AVFrame* frame,int* frame_count,AVPacket* packet,int last){
        int len,got_frame;
        char buf[1024];
    
        //进行解码操作-----------------
        //作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。
        len = avcodec_decode_video2(avctx,frame,&got_frame,packet);    //got_frame该值为0表明没有图像可以解码,否则表明有图像可以解码;
        if(len<0){
            av_log(NULL,AV_LOG_ERROR,"Fail to decode video frame %d
    ",*frame_count);
            return len;
        }
    
        if(got_frame){    //一个包中可能有1个或者多个帧,一般视频包中包含1帧;这里我们只获取1帧,进行处理,其他的依旧保留在packet结构体中,后面进行修改
            if(last)
                av_log(NULL,AV_LOG_INFO,"----Get last frame");    //一般最后一帧可能会做特殊处理
            av_log(NULL,AV_LOG_INFO,"Get frame count %3d,Saving frame %3d
    ",got_frame,*frame_count);
            snprintf(buf,sizeof(buf),"%s-%d.bmp",out_filename,*frame_count);    //图像命名
    
            //保存图像
            saveBMP(img_convert_cxt,frame,buf);
            (*frame_count)++;
        }
    
        //avcodec_decode_video2只获取了包中的一帧,然而包中可能还有其他帧,所以这里进行处理,进行结构体数据修改
        if(packet->data){
            packet->size -= len;    //大小减去1帧大小
            packet->data += len;    //数据下移,跳过已经处理的数据
        }
        return 0;
    }
    
    void decode_video(char* in_filename,char* out_filename){
        int ret,cnt=500;    //cnt长视频输出数量
    
        AVFormatContext* fmt_cxt = NULL;
    
        AVCodec* codec = NULL;
        AVCodecContext* c = NULL;
    
        struct SwsContext* img_convert_cxt = NULL;    //图像处理上下文
    
        int stream_idx;
        AVStream* st = NULL;
    
        int frameCnt = 0;    //记录解码的帧数量
        AVFrame* frame = NULL;
    
        AVPacket packet;
    
        ret = avformat_open_input(&fmt_cxt,in_filename,NULL,NULL);
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Can`t open input file:%s
    ",in_filename);
            return;
        }
    
        ret = avformat_find_stream_info(fmt_cxt,0); //获取输入流的详细信息
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Could not find stream information
    ");
            goto __AVFORMAT;
        }
        
        //媒体文件句柄 / 流类型 / 请求的流编号(-1则自动去找) / 相关流索引号(比如音频对应的视频流索引号),不指定则-1 / 如果非空,则返回所选流的解码器(指针获取) / flag当前未定义
        ret = av_find_best_stream(fmt_cxt,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Can`t find the best stream
    ");
            goto __AVFORMAT;
        }
    
        //获取流并打印流的信息
        stream_idx = ret;
        st = fmt_cxt->streams[stream_idx];
    
        av_dump_format(fmt_cxt,stream_idx,in_filename,0);
    
        //查找解码器-----------------
        codec = avcodec_find_decoder(st->codecpar->codec_id);    //根据id查找解码器,id信息存放在输入文件视频流中
        if(!codec){
            av_log(NULL,AV_LOG_ERROR,"Can`t find the decoder[%s] for input file:%s
    ",av_get_media_type_string(AVMEDIA_TYPE_VIDEO),in_filename);
            goto __AVFORMAT;
        }
    
        //打开解码器之前,先分配上下文内存-----------------
        c = avcodec_alloc_context3(NULL);
        if(!c){
            av_log(NULL,AV_LOG_ERROR,"Failed to copy %s codec parameters to decoder context
    ",av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
            goto __AVFORMAT;
        }
    
        //将解码器的参数进行设置:将输入流的参数直接拷贝即可-----------------
        ret = avcodec_parameters_to_context(c,st->codecpar);
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Cannot initialize the conversion context
    ");
            goto __ACCODEC;
        }
    
        //创建图片转换上下文(在上面参数拷贝后面)-----------------
        //源图像宽、高、像素格式;目标图像宽、高、像素格式;以及图像拉伸使用的算法
        img_convert_cxt = sws_getContext(c->width,c->height,c->pix_fmt,
                                        c->width,c->height,AV_PIX_FMT_BGR24,
                                        SWS_BICUBIC,NULL,NULL,NULL);    //后面为源、目的图像过滤器,和参数
        if(img_convert_cxt==NULL){
            av_log(NULL,AV_LOG_ERROR,"Can`t open the codec
    ");
            goto __ACCODEC;
        }
    
        //打开解码器-----------------
        ret = avcodec_open2(c,codec,NULL);
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Can`t open the codec
    ");
            goto __SWSCXT;
        }
    
        frame = av_frame_alloc();
        if(!frame){
            av_log(NULL,AV_LOG_ERROR,"Can`t alloc the frame for video
    ");
            goto __SWSCXT;
        }
    
        //开始读取数据
        av_init_packet(&packet);    //初始化数据包
        while(av_read_frame(fmt_cxt,&packet)>=0&&(--cnt)>=0){
            if(packet.stream_index == stream_idx){
                if(decode_write_frame(out_filename,c,img_convert_cxt,
                            frame,&frameCnt,&packet,0)<0){
                    av_log(NULL,AV_LOG_ERROR,"Failed to decode frame in decode_write_frame
    ");
                    goto __PACKET;
                }
    
                av_packet_unref(&packet);    //注意:减少引用不会释放空间,因为本函数还在引用这个packet结构体;
                //所以我们在解码函数中剩余的其他帧数据,还是保留在packet中的,后面根据av_read_frame继续向内部添加数据
            }
        }
        //处理packet中剩余的帧
        packet.data = NULL;
        packet.size = 0;
        decode_write_frame(out_filename,c,img_convert_cxt,
                            frame,&frameCnt,&packet,1);
    
    //下面开始处理在堆上创建的内存空间    
    __PACKET:
        av_frame_free(&frame);
        av_packet_unref(&packet);    //减少引用,使得自己释放空间
    
    __SWSCXT:
        sws_freeContext(img_convert_cxt);
    
    __ACCODEC:
        avcodec_free_context(&c);
    
    __AVFORMAT:
        avformat_close_input(&fmt_cxt);
        return;
    }
    
    int main(int argc,char* argv[]){
        av_register_all();
        av_log_set_level(AV_LOG_DEBUG);
    
        if(argc < 3){
            av_log(NULL,AV_LOG_ERROR,"The Count of Parameter must be than 2
    ");
            return -1;
        }
    
        decode_video(argv[1],argv[2]);
        return 0;
    }
    View Code
    gcc 02_ffmpeg_dec.c -o fd -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswscale
    ./fd gfxm.mp4 gfxm

    二:H264编码处理

    (一)编码流程

    avcodec_open2在解码和编码过程不同(在设置参数上):
      解码:因为输入文件中输入流本身已经设置好了参数,解码时只需要把这些参数拷贝即可,不需要手动设置
      编码:需要我们手动设置,比如分辨率...

    (二)编码实战:(YUV编码为H264)FFmpeg学习(五)H264结构

    #include <stdio.h>
    #include <libavutil/log.h>
    #include <libavcodec/avcodec.h>
    #include <libavdevice/avdevice.h>
    #include <libavformat/avformat.h>
    
    #define V_WIDTH 640
    #define V_HEIGHT 480
    
    AVFormatContext* open_dev(){
        char* devicename = "/dev/video0";    //设备文件描述符
        char errors[1024];
        int ret;
    
        AVFormatContext* fmt_ctx=NULL;    //格式上下文获取-----av_read_frame获取packet
        AVDictionary* options=NULL;
        AVInputFormat *iformat=NULL;
        AVPacket packet;    //包结构
    
        //获取输入(采集)格式
        iformat = av_find_input_format("video4linux2");    //驱动,用来录制视频
        //设置参数 ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 640*480  -framerate 15 -i /dev/video0 out.yuv
        av_dict_set(&options,"video_size","640*480",0);
        av_dict_set(&options,"framerate","30",0);
        av_dict_set(&options,"pixel_format","yuyv422",0);
    
        //打开输入设备
        ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options);    //----打开输入设备,初始化格式上下文和选项
        if(ret<0){
            av_strerror(ret,errors,1024);
            av_log(NULL,AV_LOG_ERROR,"Failed to open video device,[%d]%s
    ",ret,errors);
            return NULL;
        }
        av_log(NULL,AV_LOG_INFO,"Success to open video device
    ");
    
        return fmt_ctx;
    }
    
    //作用:编码,将yuv420转H264
    AVCodecContext* open_encoder(int width,int height){
        //------1.打开编码器
        AVCodec* codec = avcodec_find_encoder_by_name("libx264");
        if(!codec){
            av_log(NULL,AV_LOG_ERROR,"Failed to open video encoder
    ");
            return NULL;
        }
        //------2.创建上下文
        AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
        if(!codec_ctx){
            av_log(NULL,AV_LOG_ERROR,"Failed to open video encoder context
    ");
            return NULL;
        }
        //------3.设置上下文参数
        //SPS/PPS
        codec_ctx->profile = FF_PROFILE_H264_HIGH_444;    //main分支最高级别编码
        codec_ctx->level = 50;                            //表示level级别是5.0;支持最大分辨率2560×1920
        //分辨率
        codec_ctx->width = width;                        //设置分辨率--宽度
        codec_ctx->height = height;                        //设置分辨率--高度
        //GOP
        codec_ctx->gop_size = 250;                        //设置GOP个数,根据业务处理;
        codec_ctx->keyint_min = 25;                        //(option)如果GOP过大,我们就在中间多设置几个I帧,使得避免卡顿。这里表示在一组GOP中,最小插入I帧的间隔
        //B帧(增加压缩比,降低码率)
        codec_ctx->has_b_frames = 1;                    //(option)标志是否允许存在B帧
        codec_ctx->max_b_frames = 3;                    //(option)设置中间连续B帧的最大个数
        //参考帧(越大,还原性越好,但是压缩慢)
        codec_ctx->refs = 3;                            //(option)设置参考帧最大数量,缓冲队列
        //要进行编码的数据的原始数据格式(输入的原始数据)
        codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;        //注意:我们如果不进行转换yuyv422为yuv420的话,这里直接设置为AV_PIX_FMT_YUYV422P即可
        //设置码率
        codec_ctx->bit_rate = 600000;                    //设置平均码率600kpbs(根据业务)
        //设置帧率
        codec_ctx->time_base = (AVRational){1,25};        //时间基,为帧率的倒数;帧与帧之间的间隔
        codec_ctx->framerate = (AVRational){25,1};        //帧率,每秒25帧
    
        if(codec->id==AV_CODEC_ID_H264)                 //如果是h264,则可以使用预先设置好的h264参数集,压缩速度slow慢,保证质量
            av_opt_set(codec_ctx->priv_data,"preset","slow",0);
    
        //------4.打开编码器
        if(avcodec_open2(codec_ctx,codec,NULL)<0){
            av_log(NULL,AV_LOG_ERROR,"Failed to open libx264 context
    ");
            avcodec_free_context(&codec_ctx);
            return NULL;
        }
    
        return codec_ctx;
    }
    
    
    static AVFrame* initFrame(int width,int height){
        int ret;
        AVFrame* frame = av_frame_alloc();    //分配frame空间,但是数据真正被存放在buffer中
        if(!frame){
            av_log(NULL,AV_LOG_ERROR,"Failed to create frame
    ");
            return NULL;
        }
    
        //主要是设置分辨率,用来分配空间
        frame->width = width;
        frame->height = height;
        frame->format = AV_PIX_FMT_YUV420P;
    
        ret = av_frame_get_buffer(frame,32);        //第二个参数是对齐,对于音频,我们直接设置0,视频中必须为32位对齐
        if(ret<0){    //内存分配出错
            av_log(NULL,AV_LOG_ERROR,"Failed to alloc frame buffer
    ");
            av_frame_free(&frame);
            return NULL;
        }
        return frame;
    }
    
    //开始进行编码操作
    static void encode(AVCodecContext* enc_ctx,AVFrame* frame,AVPacket* newpkt,FILE* encfp){
        int len=0,got_output;
    
        int ret = avcodec_encode_video2(enc_ctx,newpkt,frame,&got_output);  //传入上下文,输出packet,输入frame,got_output表示是否产生avpacket,不是每一个frame对应一个packet,而是可能存在多个frame对应一个packet
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"avcodec_encode_video2 error! [%d] %s
    ",ret,av_err2str(ret));
            return;
        }
    
        if(got_output){
            len = fwrite(newpkt->data,1,newpkt->size,encfp);
            fflush(encfp);
            if(len!=newpkt->size){
                av_log(NULL,AV_LOG_WARNING,"Warning,newpkt size:%d not equal writen size:%d
    ",len,newpkt->size);
            }else{
                av_log(NULL,AV_LOG_INFO,"Success write newpkt to file
    ");
            }
        }
    
        av_packet_unref(newpkt);    //注意:不同于avcodec_receive_packet,我们这里需要手动处理newpkt空间,负责会出现Provided packet is too small问题
    }
    
    
    void rec_video(){
        char errors[1024];
        int ret,count=0,len,i,j,y_idx,u_idx,v_idx,base_h,base=0;
    
        AVFormatContext* fmt_ctx = NULL;
        AVCodecContext* enc_ctx = NULL;
        AVFrame* fmt = NULL;
        AVPacket packet;
    
        //打开文件,存放转换为yuv420的数据
        FILE* fp = fopen("./video.yuv","wb");
        if(fp==NULL){
            av_log(NULL,AV_LOG_ERROR,"Failed to open out file
    ");
            goto fail;
        }
    
        //打开文件,存放编码后数据(其实上面没必要存在)
        FILE* encfp = fopen("./video.h264","wb");
        if(encfp==NULL){
            av_log(NULL,AV_LOG_ERROR,"Failed to open out H264 file
    ");
            goto fail;
        }
    
    
        //打开摄像头设备的上下文格式
        fmt_ctx = open_dev();
        if(!fmt_ctx)
            goto fail;
        //打开编码上下文
        enc_ctx = open_encoder(V_WIDTH,V_HEIGHT);
        if(!enc_ctx)
            goto fail;
        //创建AVFrame
        AVFrame* frame = initFrame(V_WIDTH,V_HEIGHT);
        //创建AVPacket
        AVPacket* newpkt = av_packet_alloc();
        if(!newpkt){
            av_log(NULL,AV_LOG_ERROR,"Failed to alloc avpacket
    ");
            goto fail;
        }
    
        //开始从设备中读取数据
        while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<100){
            av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d
    ",packet.size,packet.data,count);
    
            //------先将YUYV422数据转YUV420数据(重点)
            //序列为YU YV YU YV,一个yuv422帧的长度 width * height * 2 个字节
            //丢弃偶数行 u v
    
            //先存放Y数据
            memset(frame->data[0],0,V_WIDTH*V_HEIGHT*sizeof(char));
            for(i=0,y_idx=0;i<2*V_HEIGHT*V_WIDTH;i+=2){
                frame->data[0][y_idx++]=packet.data[i];
            }
            //再获取U、V数据
            memset(frame->data[1],0,V_WIDTH*V_HEIGHT*sizeof(char)/4);
            memset(frame->data[2],0,V_WIDTH*V_HEIGHT*sizeof(char)/4);
            for(i=0,u_idx=0,v_idx=0;i<V_HEIGHT;i+=2){    //丢弃偶数行,注意:i<V_HEIGHT*2,总数据量是Y+UV,可以达到V_HEIGHT*2行
                base_h = i*2*V_WIDTH;                    //获取奇数行开头数据位置
                for(j=0;j<V_WIDTH*2;j+=4){                //遍历这一行数据,每隔4个为1组 y u y v
                    frame->data[1][u_idx++] = packet.data[base_h+j+1];    //获取U数据
                    frame->data[2][v_idx++] = packet.data[base_h+j+3];    //获取V数据
                }
            }
            
            //写入yuv420数据
            fwrite(frame->data[0],1,V_WIDTH*V_HEIGHT,fp);
            fwrite(frame->data[1],1,V_WIDTH*V_HEIGHT/4,fp);
            fwrite(frame->data[2],1,V_WIDTH*V_HEIGHT/4,fp);
    
            //开始编码
            frame->pts = base++;    //重点:对帧的pts进行顺序累加;不能设置随机值;H264要求编码的帧的pts是连续的值
            encode(enc_ctx,frame,newpkt,encfp);
    
            //释放空间
            av_packet_unref(&packet);
        }
        encode(enc_ctx,NULL,newpkt,encfp);    //告诉编码器编码结束,将后面剩余的数据全部写入即可
    fail:
        if(fp)
            fclose(fp);
        if(encfp)
            fclose(encfp);
    
        if(frame)
            av_frame_free(&frame);
        if(newpkt)
            av_packet_free(&newpkt);
    
        //关闭设备、释放上下文空间
        if(enc_ctx)
            avcodec_free_context(&enc_ctx);
        avformat_close_input(&fmt_ctx);
        return ;
    }
    
    int main(int argc,char* argv)
    {
    
        av_register_all();
        av_log_set_level(AV_LOG_DEBUG);
        //注册所有的设备,包括我们需要的音频设备
        avdevice_register_all();
    
        rec_video();
        return 0;
    }
    View Code
    gcc 01_ffmpeg_enc.c -o fe -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice

    三:AAC解码处理

    回顾:FFmpeg学习(三)音频基础重采样

    解码方式同上面的H264解码,不同的是解码函数:

    /*@param avctx编解码器上下文
     *@param [out] frame用于存储解码音频样本的AVFrame
     *@param [out] got_frame_ptr如果没有帧可以解码则为零,否则为非零
     *@param [in] avpkt包含输入缓冲区的输入AVPacket
     *@return 如果在解码期间发生错误,则返回否定错误代码,否则返回从输入AVPacket消耗的字节数。
     */
    int avcodec_decode_audio4 ( AVCodecContext * avctx, AVFrame * frame, int * got_frame_ptr, const AVPacket * avpkt )

    (一)获取AAC数据

    FFmpeg学习(二)FFmpeg命令学习

    ffmpeg -i out.mp4 -acodec copy -vn out.aac

    一个AAC原始帧包含一段时间内1024个采样及相关数据!!

    AAC:

    音频帧的播放时间=一个AAC帧对应的采样样本的个数/采样频率(单位为s)
    
    一帧 1024个 sample。采样率 Samplerate 44100Hz,每秒44100个sample, 所以根据公式 音频帧的播放时间=一个AAC帧对应的采样样本的个数/采样频率
    
    当前AAC一帧的播放时间是= 1024*1000/44100= 22.32ms(单位为ms)

    MP3:

    mp3 每帧均为1152个字节, 则:
    
    frame_duration = 1152 * 1000 / sample_rate
    
    例如:sample_rate = 44100HZ时,计算出的时长为26.122ms,这就是经常听到的mp3每帧播放时间固定为26ms的由来。

    (二)编程实战(AAC解码为PCM数据)

    #include <stdio.h>
    #include <libavutil/log.h>
    #include <libavformat/avformat.h>
    #include <libswresample/swresample.h>
    
    #define MAX_AUDIO_FRAME_SIZE  192000
    
    SwrContext* getSwrCxt(AVCodecContext* c,uint8_t** dst,int* dst_size,int* ret){
        SwrContext* swr_cxt = NULL;
    
        //------开始设置解码转换参数
        uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;  //输入数据的通道布局,是双声道
        int out_nb_samples = 1024;                          //采样个数
        enum AVSampleFormat sample_fmt = AV_SAMPLE_FMT_S16;
        int out_sample_rate = 44100;
        int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
        *dst_size = av_samples_get_buffer_size(NULL,out_channels,out_nb_samples,sample_fmt,1);    //这是每次采样的数据 = 通道数×每个通道采样(每帧)×格式
    
        *dst = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE*2);  //最大开辟的空间
        if(*dst==NULL){
            av_log(NULL,AV_LOG_ERROR,"Can`t alloc dst memory!
    ");
            *ret = -3;
            return NULL;
        }
    
        int64_t in_channel_layout = av_get_default_channel_layout(c->channels);
    
        //----------创建重采样的上下文
        swr_cxt = swr_alloc_set_opts(NULL,                    //设置已经创建好的上下文,如果没有,则为NULL
                                    out_channel_layout,       //设置输出目标的通道布局(双声道,立体声,...,方位增宽)
                                    sample_fmt,               //设置输出目标的采样格式,设置为32位浮点型
                                    out_sample_rate,          //设置输出目标的采样率
                                    in_channel_layout,        //输入数据的通道布局,是双声道
                                    c->sample_fmt,            //输入数据的采样格式为s16le
                                    c->sample_rate,           //输入的采样率
                                    0,                        //日志级别
                                    NULL);                    //日志上下文
        if(!swr_cxt){
            av_log(NULL,AV_LOG_ERROR,"Failed to set swr context
    ");
            *ret = -1;
            return NULL;
        }
    
        //----------初始化上下文
        if(swr_init(swr_cxt)<0){
            av_log(NULL,AV_LOG_ERROR,"Failed to initial swr context
    ");
            *ret = -2;
            return NULL;
        }
        return swr_cxt;
    }
    
    void dec_audio(char* in_filename,char* out_filename){
        int ret,stream_idx,cnt=1;
        int got_aac;
        FILE* fp = NULL;
    
        AVFormatContext* fmt_ctx=NULL;
    
        AVCodec* codec = NULL;
        AVCodecContext* c = NULL;
        SwrContext* swr_cxt = NULL;
    
        AVPacket* packet;    //包结构
        AVFrame* frame;
    
        uint8_t* dst;
        int dst_size;
    
        ret = avformat_open_input(&fmt_ctx,in_filename,NULL,NULL);
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Can`t open input file:%s
    ",in_filename);
            return;
        }
    
        ret = avformat_find_stream_info(fmt_ctx,0); //获取输入流的详细信息
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Can`t find stream information!
    ");
            goto __AVFORMAT;
        }
    
        //媒体文件句柄 / 流类型 / 请求的流编号(-1则自动去找) / 相关流索引号(比如音频对应的视频流索引号),不指定则-1 / 如果非空,则返回所选流的解码器(指针获取) / flag当前未定义
        ret = av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Can`t find the best stream!
    ");
            goto __AVFORMAT;
        }
        stream_idx = ret;
    
        c = fmt_ctx->streams[stream_idx]->codec;    //获取输入流的编解码上下文
        codec = avcodec_find_decoder(c->codec_id);  //获取输入流对应的解码器
    
        if(!codec){
            av_log(NULL,AV_LOG_ERROR,"Can`t find the decoder!
    ");
            goto __AVFORMAT;
        }
    
        //打开解码器
        ret = avcodec_open2(c,codec,NULL);
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Can`t open avcodec!
    ");
            goto __AVFORMAT;
        }
    
        swr_cxt = getSwrCxt(c,&dst,&dst_size,&ret);
        if(!swr_cxt){
            if(ret==-2)
                goto __SWRCXT;
            goto __AVFORMAT;
        }
    
        packet = av_packet_alloc();
        if(!packet){
            av_log(NULL,AV_LOG_ERROR,"Can`t alloc packet!
    ");
            goto __SWRCXT;
        }
        av_init_packet(packet);
    
        frame = av_frame_alloc();
        if(!frame){
            av_log(NULL,AV_LOG_ERROR,"Can`t alloc packet!
    ");
            goto __PACKET;
        }
    
        //打开文件
        fp = fopen(out_filename,"wb");
        if(fp==NULL){
            av_log(NULL,AV_LOG_ERROR,"Failed to open out file
    ");
            goto __FRAME;
        }
    
        //开始从设备中读取数据
        while(av_read_frame(fmt_ctx,packet)>=0){
            if(packet->stream_index == stream_idx){
                av_log(NULL,AV_LOG_INFO,"decode %d aac frame to pcm
    ",cnt++);
                //开始解码aac数据为pcm
                ret = avcodec_decode_audio4(c,frame,&got_aac,packet);
                if(ret<0){
                    av_log(NULL,AV_LOG_ERROR,"Failed to open out file
    ");
                    goto fail;
                }
                //转码
                if(got_aac>0){
                    swr_convert(swr_cxt,                                 //上下文
                                &dst,                                    //输出数组(双指针)
                                MAX_AUDIO_FRAME_SIZE,                    //每个通道的采样数
                                (const uint8_t**)frame->data,            //输入数据,来自与packet.data,要改造格式
                                frame->nb_samples);                      //输入的通道采样数
                    fwrite(dst,1,dst_size,fp);
                }
                got_aac = 0;
    
            }
    
            //释放空间
            av_free_packet(packet);
        }
    
    //----------释放空间
    fail:
        fclose(fp);
    __FRAME:
        av_frame_free(&frame);
    __PACKET:
        av_free_packet(packet);
    __SWRCXT:
        av_freep(&dst);
        swr_free(&swr_cxt);
    __AVFORMAT:
        //关闭设备、释放上下文空间
        avformat_close_input(&fmt_ctx);
        return ;
    }
    
    int main(int argc,char* argv[])
    {
        av_register_all();
        av_log_set_level(AV_LOG_DEBUG);
    
        if(argc<=2){
            av_log(NULL,AV_LOG_ERROR,"The Count of Parameter must be than 2
    ");
            return -1;
        }
    
        dec_audio(argv[1],argv[2]);
        return 0;
    }
    View Code
    gcc -o fad 04_ffmpeg_aac_dec.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswresample
    ./fad out.aac out.pcm
    ffplay out.pcm -ar 44100 -ac 2 -f s16le

    四:AAC编码处理

    回顾:FFmpeg学习(三)音频基础

    (一)编程实战(PCM转AAC数据)

    参考:FFmpeg音频编码 ---- pcm转aac

    https://www.jianshu.com/p/b16fac8e05b6

    ffmpeg -i out.aac -ar 48000 -ac 2 -f f32le 48000_2_f32le.pcm
    #include <libavutil/log.h>
    #include <libavcodec/avcodec.h>
    
    static int check_sample_fmt(const AVCodec* codec,enum AVSampleFormat sample_fmt){
        const enum AVSampleFormat* p = codec->sample_fmts;
        while(*p!=AV_SAMPLE_FMT_NONE){  // 通过AV_SAMPLE_FMT_NONE作为结束符
            av_log(NULL,AV_LOG_INFO,"codec[%s] support sample fromat: %s
    ",codec->name,av_get_sample_fmt_name(*p));
            if(*p==sample_fmt)
                return 1;
            p++;
        }
        return 0;
    }
    
    static int check_sample_rate(const AVCodec* codec,const int sample_rate){
        const int* p = codec->supported_samplerates;
        while(*p!=0){   // 0作为退出条件,比如libfdk-aacenc.c的aac_sample_rates
            av_log(NULL,AV_LOG_INFO,"codec[%s] support sample rate: %dhz
    ",codec->name,*p);
            if(*p==sample_rate)
                return 1;
            p++;
        }
        return 0;
    }
    
    static int check_sample_channel_layout(const AVCodec* codec,const uint64_t channel_layout){
        const uint64_t* p = codec->channel_layouts;
        if(!p){         // 不是每个codec都给出支持的channel_layout
            av_log(NULL,AV_LOG_ERROR,"codec[%s] not set channel layout
    ",codec->name);
            return 1;
        }
        while(*p!=0){    // 0作为退出条件,比如libfdk-aacenc.c的aac_channel_layout
            av_log(NULL,AV_LOG_INFO,"codec[%s] support channel layout: %d
    ",codec->name,*p);
            if(*p==channel_layout)
                return 1;
            p++;
        }
        return 0;
    }
    
    static int check_codec(AVCodec* codec,AVCodecContext* codec_ctx){
        //检查格式
        if(!check_sample_fmt(codec,codec_ctx->sample_fmt)){
            av_log(NULL,AV_LOG_ERROR,"codec[%s] not support sample fromat: %d
    ",codec->name,
                av_get_sample_fmt_name(codec_ctx->sample_fmt));
            return 0;
        }
        //检查比特率
        if(!check_sample_rate(codec,codec_ctx->sample_rate)){
            av_log(NULL,AV_LOG_ERROR,"codec[%s] not support sample rate: %dhz
    ",codec->name,codec_ctx->sample_rate);
            return 0;
        }
        //检查通道布局
        if(!check_sample_channel_layout(codec,codec_ctx->channel_layout)){
            av_log(NULL,AV_LOG_ERROR,"codec[%s] not support channel layout: %d
    ",codec->name,codec_ctx->channel_layout);
            return 0;
        }
    
        //打印所有配置
        av_log(NULL,AV_LOG_INFO,"
    
    codec[%s] encode config
    ",codec->name);
        av_log(NULL,AV_LOG_INFO,"bit_rate:%ldkbps
    ",codec_ctx->bit_rate/1024);
        av_log(NULL,AV_LOG_INFO,"sample_rate:%d
    ",codec_ctx->sample_rate);
        av_log(NULL,AV_LOG_INFO,"sample_fmt:%s
    ",av_get_sample_fmt_name(codec_ctx->sample_fmt));
        av_log(NULL,AV_LOG_INFO,"channels:%d
    ",codec_ctx->channels);
        av_log(NULL,AV_LOG_INFO,"aac frame_size:%d
    
    ",codec_ctx->frame_size);
    
        return 1;
    }
    
    AVCodecContext* getCodeCtx(int* ret){
        //---------1.打开编码器
        enum AVCodecID codec_id = AV_CODEC_ID_AAC;
        AVCodec* codec = avcodec_find_encoder(codec_id);
        //AVCodec* codec = avcodec_find_encoder_by_name("libfdk_aac");    //内部要求的采样大小就是是s16le------重点
        if(!codec){
            av_log(NULL,AV_LOG_ERROR,"Codec not found
    ");
            *ret = -1;
            return NULL;
        }
        //---------2.创建上下文
        AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
        if(!codec_ctx){
            av_log(NULL,AV_LOG_ERROR,"Codec can`t alloc context
    ");
            *ret = -1;
            return NULL;
        }
        //------------------设置上下文参数
        codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;         //设置编码类型,音频编码,还可以设置编码器id codec_id等等
        codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;         //设置采样格式---------(该字段是被固定了),上面说到,libfdk_aac处理16位,所以设置为16位。所以我们一般是将其他格式进行重采样为16位
        codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;    //设置通道布局,双通道
        codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);                            //设置通道数(其实和上面一样)
        codec_ctx->sample_rate = 48000;                     //设置采样率
        codec_ctx->profile = FF_PROFILE_AAC_LOW;            //设置AAC编码格式,如果设置了这个字段,就不需要设置下面的比特率了
        codec_ctx->bit_rate = 128*1024;                     //设置比特率,128k;对于每个编码方式,都有最低编码码率。AAC_LC:128k AAC HE:64k AAC HE V2:32k
    
        //---------------检查是否支持采样格式信息
        if(!check_codec(codec,codec_ctx)){
            av_log(NULL,AV_LOG_ERROR,"Codec can`t pass check
    ");
            *ret = -2;
            return codec_ctx;
        }
    
        //---------3.打开编码器
        if(avcodec_open2(codec_ctx,codec,NULL)<0){
            av_log(NULL,AV_LOG_ERROR,"Codec can`t be open
    ");
            *ret = -2;
            return codec_ctx;
        }
    
        //输出一下aac每次采样点个数(1024)
        av_log(NULL,AV_LOG_INFO,"aac frame_size:%d
    ",codec_ctx->frame_size);   //frame_size---每帧单个通道的采样点!!!!!!!!!!
        *ret = 0;
        return codec_ctx;
    }
    
    AVFrame* initFrame(AVCodecContext* codec_ctx){
        AVFrame* frame = av_frame_alloc();    //分配frame空间,但是数据真正被存放在buffer中
        if(!frame){
            av_log(NULL,AV_LOG_ERROR,"Failed to create frame
    ");
            return NULL;
        }
    
        //配置3要素,才能为buffer分配空间
        frame->nb_samples = codec_ctx->frame_size;          //每帧数据中,单通道采样数,和前面重采样配置一样
        frame->format = codec_ctx->sample_fmt;              //采样大小
        frame->channel_layout = codec_ctx->channel_layout;  //通道布局
        av_frame_get_buffer(frame,0);        //第二个参数是对齐  
        if(!frame->data[0]){
            av_log(NULL,AV_LOG_ERROR,"Failed to create frame buffer
    ");
            return NULL;
        }
        return frame;
    }
    
    void f32le_convert_to_fltp(float* f32le,float* fltp,int nb_samples){
        float* fltp_l = fltp;               //左通道
        float* fltp_r = fltp+nb_samples;    //右通道
        for(int i=0;i<nb_samples;i++){
            fltp_l[i] = f32le[i*2];
            fltp_r[i] = f32le[i*2+1];
        }
    }
    
    static void get_adts_header(AVCodecContext *ctx, uint8_t *adts_header, int aac_length)
    {
        uint8_t freq_idx = 0;    //0: 96000 Hz  3: 48000 Hz 4: 44100 Hz
        switch (ctx->sample_rate) {
            case 96000: freq_idx = 0; break;
            case 88200: freq_idx = 1; break;
            case 64000: freq_idx = 2; break;
            case 48000: freq_idx = 3; break;
            case 44100: freq_idx = 4; break;
            case 32000: freq_idx = 5; break;
            case 24000: freq_idx = 6; break;
            case 22050: freq_idx = 7; break;
            case 16000: freq_idx = 8; break;
            case 12000: freq_idx = 9; break;
            case 11025: freq_idx = 10; break;
            case 8000: freq_idx = 11; break;
            case 7350: freq_idx = 12; break;
            default: freq_idx = 4; break;
        }
        uint8_t chanCfg = ctx->channels;
        uint32_t frame_length = aac_length + 7;
        adts_header[0] = 0xFF;
        adts_header[1] = 0xF1;
        adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);
        adts_header[3] = (((chanCfg & 3) << 6) + (frame_length  >> 11));
        adts_header[4] = ((frame_length & 0x7FF) >> 3);
        adts_header[5] = (((frame_length & 7) << 5) + 0x1F);
        adts_header[6] = 0xFC;
    }
    
    static int encode(AVCodecContext* codec_ctx,AVFrame* frame,AVPacket* pkt,FILE* out_fp){
        int ret,len;
    
        //--------进行编码
        ret = avcodec_send_frame(codec_ctx,frame);    
        if(ret<0){
            av_log(NULL,AV_LOG_ERROR,"Error sending the frame to the encoder
    ");
            return -1;
        }
    
        // 编码和解码都是一样的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOF
        while(ret >= 0){
            ret = avcodec_receive_packet(codec_ctx,pkt);    //获取编码后的数据放入packet中
            if(ret<0){
                if(ret==AVERROR(EAGAIN)||ret==AVERROR_EOF){    //读完数据
                    return 0;
                }else{    //编码器出错
                    av_log(NULL,AV_LOG_ERROR,"avcodec_receive_packet error! [%d] %s
    ",ret,av_err2str(ret));
                    return -1;
                }
            }
    
            uint8_t aac_header[7];
            get_adts_header(codec_ctx,aac_header,pkt->size);
            len = fwrite(aac_header,1,7,out_fp);
            fflush(out_fp);
            if(len!=7){
                av_log(NULL,AV_LOG_WARNING,"Warning,ADTS header write error!
    ");
            }
    
            len = fwrite(pkt->data,1,pkt->size,out_fp);
            fflush(out_fp);
            if(len!=pkt->size){
                av_log(NULL,AV_LOG_WARNING,"Warning,newpkt size:%d not equal writen size:%d
    ",len,pkt->size);
            }else{
                av_log(NULL,AV_LOG_INFO,"Success write newpkt to file
    ");
            }
        }
        return -1;
    }
    
    void enc_audio2(char* in_filename,char* out_filename){
        const char* in_pcm_file = in_filename;      // 输入PCM文件
        const char* out_aac_file = out_filename;     // 输出的AAC文件
        int ret;
        // 2.分配内存
        AVCodecContext *codec_ctx = getCodeCtx(&ret);
    
        // 5.打开输入和输出文件
        FILE *infile = fopen(in_pcm_file, "rb");
        if (!infile) {
            fprintf(stderr, "Could not open %s
    ", in_pcm_file);
            exit(1);
        }
        FILE *outfile = fopen(out_aac_file, "wb");
        if (!outfile) {
            fprintf(stderr, "Could not open %s
    ", out_aac_file);
            exit(1);
        }
    
        // 6.分配packet
        AVPacket *pkt = av_packet_alloc();
        if (!pkt) {
            fprintf(stderr, "could not allocate the packet
    ");
            exit(1);
        }
    
        // 7.分配frame
        AVFrame *frame = av_frame_alloc();
        if (!frame) {
            fprintf(stderr, "Could not allocate audio frame
    ");
            exit(1);
        }
        /* 每次送多少数据给编码器由:
         *  (1)frame_size(每帧单个通道的采样点数);
         *  (2)sample_fmt(采样点格式);
         *  (3)channel_layout(通道布局情况);
         * 3要素决定
         */
        frame->nb_samples     = codec_ctx->frame_size;
        frame->format         = codec_ctx->sample_fmt;
        frame->channel_layout = codec_ctx->channel_layout;
        frame->channels = av_get_channel_layout_nb_channels(frame->channel_layout);
        printf("frame nb_samples:%d
    ", frame->nb_samples);
        printf("frame sample_fmt:%d
    ", frame->format);
        printf("frame channel_layout:%lu
    
    ", frame->channel_layout);
    
        // 8.为frame分配buffer
        ret = av_frame_get_buffer(frame, 0);
        if (ret < 0) {
            fprintf(stderr, "Could not allocate audio data buffers
    ");
            exit(1);
        }
    
        // 9.循环读取数据
        // 计算出每一帧的数据 单个采样点的字节 * 通道数目 * 每帧采样点数量
        int frame_bytes = av_get_bytes_per_sample(frame->format) 
                * frame->channels 
                * frame->nb_samples;
        printf("frame_bytes %d
    ", frame_bytes);
        uint8_t *pcm_buf = (uint8_t *)malloc(frame_bytes);
        if(!pcm_buf) {
            printf("pcm_buf malloc failed
    ");
            return 1;
        }
        uint8_t *pcm_temp_buf = (uint8_t *)malloc(frame_bytes);
        if(!pcm_temp_buf) {
            printf("pcm_temp_buf malloc failed
    ");
            return 1;
        }
        int64_t pts = 0;
        printf("start enode
    ");
        for (;;) {
            memset(pcm_buf, 0, frame_bytes);
            size_t read_bytes = fread(pcm_buf, 1, frame_bytes, infile);
            if(read_bytes <= 0) {
                printf("read file finish
    ");
                break;
            }
    
            // 10.确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份 目的是新写入的数据和编码器保存的数据不能产生冲突
            ret = av_frame_make_writable(frame);
            if(ret != 0)
                printf("av_frame_make_writable failed, ret = %d
    ", ret);
    
            // 11.填充音频帧
            if(AV_SAMPLE_FMT_S16 == frame->format) {
                // 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
                ret = av_samples_fill_arrays(frame->data, frame->linesize,
                                       pcm_buf, frame->channels,
                                       frame->nb_samples, frame->format, 0);
            } else {
                // 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
                // 将本地的f32le packed模式的数据转为float palanar
                memset(pcm_temp_buf, 0, frame_bytes);
                f32le_convert_to_fltp((float *)pcm_buf, (float *)pcm_temp_buf, frame->nb_samples);
                ret = av_samples_fill_arrays(frame->data, frame->linesize,
                                       pcm_temp_buf, frame->channels,
                                       frame->nb_samples, frame->format, 0);
            }
    
            // 12.编码
            pts += frame->nb_samples;
            frame->pts = pts;       // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
            ret = encode(codec_ctx, frame, pkt, outfile);
            if(ret < 0) {
                printf("encode failed
    ");
                break;
            }
        }
    
        // 13.冲刷编码器
        encode(codec_ctx, NULL, pkt, outfile);
    
        // 14.关闭文件
        fclose(infile);
        fclose(outfile);
    
        // 15.释放内存
        if(pcm_buf) {
            free(pcm_buf);
        }
        if (pcm_temp_buf) {
            free(pcm_temp_buf);
        }
        av_frame_free(&frame);
        av_packet_free(&pkt);
        avcodec_free_context(&codec_ctx);
        printf("main finish, please enter Enter and exit
    ");
        return 0;
    }
    
    void enc_audio(char* in_filename,char* out_filename){
        int ret,len;
        int64_t pts = 0;
        int frame_bytes;
        uint8_t* pcm_buf,* pcm_temp_buf; //存放pcm数据
        FILE* in_fp,* out_fp;    
    
        AVCodecContext* c = NULL;
    
        AVPacket* pkt = NULL;
        AVFrame* frame = NULL;
    
        c = getCodeCtx(&ret);
        if(ret<0){
            if(ret==-2)
                goto __CODECCTX;
            return;
        }
    
        pkt = av_packet_alloc();
        if(!pkt){
            av_log(NULL,AV_LOG_ERROR,"Can`t alloc memory for packet
    ");
            goto __CODECCTX;
        }
    
        frame = initFrame(c);
        if(!frame){
            av_log(NULL,AV_LOG_ERROR,"Can`t alloc memory for frame
    ");
            goto __PACKET;
        }
    
        //-------开始从pcm文件中读取数据
        //获取每一帧的数据大小 !!!!
        //单个采样点所需字节×通道数×每帧下单通道采样点数量
        frame_bytes = av_get_bytes_per_sample(frame->format)*frame->channels*frame->nb_samples;
        av_log(NULL,AV_LOG_INFO,"frame_bytes:%d
    ",frame_bytes);
    
        pcm_buf = (uint8_t*)malloc(frame_bytes);
        if(!pcm_buf){
            av_log(NULL,AV_LOG_ERROR,"Can`t alloc memory for pcm_buf
    ");
            goto __FRAME;
        }
    
        pcm_temp_buf = (uint8_t*)malloc(frame_bytes);
        if(!pcm_temp_buf){
            av_log(NULL,AV_LOG_ERROR,"Can`t alloc memory for pcm_temp_buf
    ");
            goto __PCMBUF;
        }
    
        in_fp = fopen(in_filename,"rb");
        if(!in_fp){
            av_log(NULL,AV_LOG_ERROR,"Can`t open file:%s
    ",in_filename);
            goto __PCMTEMPBUF;
        }
    
        out_fp = fopen(out_filename,"wb");
        if(!out_fp){
            av_log(NULL,AV_LOG_ERROR,"Can`t open file:%s
    ",out_filename);
            goto __INFILE;
        }
    
        av_log(NULL,AV_LOG_INFO,"
    Start encode
    ");
        while(1){
            memset(pcm_buf,0,frame_bytes);  //初始化空间,开始读取文件
            len = fread(pcm_buf,1,frame_bytes,in_fp);
            if(len<=0){
                av_log(NULL,AV_LOG_WARNING,"read file finsih
    ");
                break;
            }
    
            //----------确保frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份 目的是新写入的数据和编码器保存的数据不能产生冲突
            ret = av_frame_make_writable(frame);
            if(ret!=0){
                av_log(NULL,AV_LOG_WARNING,"av_frame_make_writable failed, ret = %d
    ", ret);
    
            }
    
            //----------开始填充音频帧
             // 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
             /*
                AV_SAMPLE_FMT_S16格式,也就是两个声道交替存储,每个样点2个字节。
                而FFmpeg默认的AAC编码器不支持这种格式的编码,
                只支持AV_SAMPLE_FMT_FLTP,这种格式是按平面存储,样点是float类型,
                所谓平面也就是每个声道单独存储,比如左声道存储到data[0]中,右声道存储到data[1]中。
            */
            //ffmpeg只能提取packed格式的PCM数据,在编码时候如果输入要为fltp则需要进行转换
            //flt格式:ffmpeg -i buweishui.aac -ar 48000 -ac 2 -f f32le 48000_2_f32le.pcm
            //s16格式:ffmpeg -i buweishui.aac -ar 48000 -ac 2 -f s16le 48000_2_s16le.pcm
            //然而我们设置的frame格式就是AV_SAMPLE_FMT_FLTP格式,所以需要对其进行转换
            memset(pcm_temp_buf,0,frame_bytes);
            f32le_convert_to_fltp((float *)pcm_buf, (float *)pcm_temp_buf,frame->nb_samples);
            ret = av_samples_fill_arrays(frame->data,frame->linesize,
                pcm_temp_buf,
                frame->channels,frame->nb_samples,frame->format,0);  // 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
            
            //----------开始编码
            pts += frame->nb_samples;
            frame->pts = pts;   //重点:对帧的pts进行顺序累加;不能设置随机值;要求编码的帧的pts是连续的值
            ret = encode(c,frame,pkt,out_fp);
            if(ret<0){
                av_log(NULL,AV_LOG_WARNING,"encode error!
    ");
                break;
            }
        }
    
        //--------冲刷编码器
        encode(c,NULL,pkt,out_fp);
    
    
    __FINSIH:
        fclose(out_fp);
    __INFILE:
        fclose(in_fp);
    __PCMTEMPBUF:
        if(pcm_temp_buf)
            free(pcm_temp_buf);
    __PCMBUF:
        if(pcm_buf)
            free(pcm_buf);
    __FRAME:
        av_frame_free(&frame);
    __PACKET:
        av_packet_free(&pkt);
    __CODECCTX:
        avcodec_free_context(&c);
    }
    
    int main(int argc, char* argv[])
    {
        av_log_set_level(AV_LOG_DEBUG);
        if(argc<=2){
            av_log(NULL,AV_LOG_ERROR,"The Count of Parameter must be than 2
    ");
            return -1;
        }
    
        enc_audio(argv[1],argv[2]);
        return 0;
    }
    View Code
    gcc 03_ffmpeg_aac_enc.c -o fae -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswscale -lswresample
    ./fae 48000_2_f32le.pcm out3.aac

    对于格式转换最好使用swr_convert函数

  • 相关阅读:
    OTA JAR和JAD的mime不同
    document.getElementById('selCatalog').remove(i)突然无效???!
    判断WAP1.1和WAP2.0并解析为wml或xhtml
    IE和firefox下显示html内容
    unixrisk tip
    unixftp windows
    unixstdin/stdout/stderr
    峰鸟摄影
    linuxgrep commond
    unixtutorial(recommended)
  • 原文地址:https://www.cnblogs.com/ssyfj/p/14722272.html
Copyright © 2011-2022 走看看