首页 > 其他分享 >如何使用libavcodec将.yuv图像序列编码为.h264的视频码流?

如何使用libavcodec将.yuv图像序列编码为.h264的视频码流?

时间:2023-06-15 10:55:36浏览次数:44  
标签:码流 int32 libavcodec frame ctx yuv width codec size

1.实现打开和关闭输入文件和输出文件的操作

点击查看代码
//io_data.cpp
static FILE* input_file= nullptr;
static FILE* output_file= nullptr;
int32_t open_input_output_files(const char* input_name,const char* output_name){
    if(strlen(input_name)==0||strlen(output_name)==0){
        cout<<"Error:empty input or output file name."<<endl;
        return -1;
    }
    close_input_output_files();
    input_file=fopen(input_name,"rb");//rb:读取一个二进制文件,该文件必须存在
    if(input_file==nullptr){
        cerr<<"Error:failed to open input file."<<endl;
        return -1;
    }
    output_file=fopen(output_name,"wb");//wb:打开或新建一个二进制文件,只允许写
    if(output_file== nullptr){
        cout<<"Error:failed to open output file."<<endl;
        return -1;
    }
    return 0;
}
void close_input_output_files(){
    if(input_file!= nullptr){
        fclose(input_file);
        input_file= nullptr;
    }
    if(output_file!= nullptr){
        fclose(output_file);
        output_file= nullptr;
    }
}
2.视频编码器的初始化 在介绍这部分内容之前,先来了解一下几个非常重要的结构体:AVCodec,AVCodecContext,AVPacket以及AVFrame。 AVCodec: AVCodec类型的结构包含了FFmpeg libavcodec对一个编码器底层实现的封装,其内部定义的部分结构如下:
点击查看代码
typedef struct AVCodec{
          const char *name;//简要名称
          const char *longname;//完整名称
          enum AVMediaType type;//媒体类型
          enum AVCodecID id;
          enum AVPixelFormat *pix_fmts;//像素格式,一般为yuv420p
          const AVProfile *profiles;//编码档次
       }
AVCodecContext: 在FFmpeg中,每一个编码器都对应一个上下文结构;在编码开始前,可以通过该结构配置相应的编码参数,比如:编码的profile,图像的宽和高,关键帧间距,码率和帧率等。对于其他编码器(如libx264)的私有参数,AVCodecContext结构可以使用成员priv_data保存编码器的配置信息。该结构的部分定义如下:
点击查看代码
 typedef struct AVCodecContext{
        void *priv_data;//私有参数
        int64_t bit_rate;//码率
        int width,height;
        enum AVPixelFormat pix_fmt;
        int max_b_frames;//最大的b帧数量
 }
AVFrame: 在FFmpeg中,未压缩的图像用AVFrame结构来表示;针对编码器,其流程为从数据源获取图像格式的输入数据,保存为AVFrame对象并传入编码器,从编码器中输出AVPacket结构。在AVFrame结构中,所包含的最重要的结构即图像数据的缓存区。待编码图像的像素数据保存在AVFrame结构的data指针所指向的内存区。在保存图像像素数据时,存储区的 宽度有时会大于图像的宽度,这时可以在每一行像素的末尾填充字节。此时,存储区的宽度可以通过AVFrame的linesize获取。该结构的常用成员如下:
点击查看代码
typedef struct AVFrame{
        #define AV_NUM_DATA_POINTERS 8
        uint8_t *data[AV_NUM_DATA_POINTERS];//图像数据缓存区
        int linesize[AV_NUM_DATA_POINTERS];//存储区的宽度
        int width,height;
        int format;
     }
AVPacket: AVPacket结构用于保存未解码的二进制码流的一个数据包,在该结构中,码流数据保存在data指针指向的内存区中,数据长度为size字节。在从编码器获取到输出的AVPacket结构后,可以通过data指针和size值读取编码后的码流。常用的结构如下:
点击查看代码
 typedef struct AVPacket{
        int64_t pts;//显示时间戳
        int64_t dts;//解码时间戳
        uint8_t *data;//码流数据
        int size;
        int stream_index;//所从属的stream序号
     }
编码器初始化的代码如下:
点击查看代码
//video_encoder_core.cpp
static const AVCodec* codec= nullptr;
static AVCodecContext* codec_ctx= nullptr;
static AVFrame* frame= nullptr;
static AVPacket* pkt= nullptr;
int32_t init_video_encoder(const char* codec_name){
    if(strlen(codec_name)==0){
        cerr<<"Error:empty codec name."<<endl;
        return -1;
    }
    //查找编码器
    codec=avcodec_find_encoder_by_name(codec_name);
    if(!codec){
        cerr<<"Error:could not find codec with codec name:"<<string(codec_name)<<endl;
        return -1;
    }
    //创建编码器上下文结构的实例
    codec_ctx= avcodec_alloc_context3(codec);
    if(!codec_ctx){
        cerr<<"Error:could not allocate video codec context."<<endl;
        return -1;
    }
    //配置编码参数
    codec_ctx->profile=FF_PROFILE_H264_HIGH;
    codec_ctx->bit_rate=2000000;
    codec_ctx->width=1920;
    codec_ctx->height=1080;
    codec_ctx->gop_size=10;//关键帧间距
    codec_ctx->time_base=(AVRational){1,25};//num:分子,den:分母
    codec_ctx->framerate=(AVRational){25,1};
    codec_ctx->max_b_frames=3;
    codec_ctx->pix_fmt=AV_PIX_FMT_YUV420P;
    if(codec->id==AV_CODEC_ID_H264){
        av_opt_set(codec_ctx->priv_data,"preset","slow",0);
        av_opt_set(codec_ctx->priv_data,"tune","zerolatency",0);
    }
    //使用指定的codec初始化编码器上下文结构,并分配内存
    int32_t result=avcodec_open2(codec_ctx,codec, nullptr);
    if(result<0){
        cerr<<"Error:could not open codec"<<endl;
        return -1;
    }
    pkt=av_packet_alloc();
    if(!pkt){
        cerr<<"Error:could not allocate AVPacket."<<endl;
        return -1;
    }
    frame=av_frame_alloc();
    if(!frame){
        cerr<<"Error:could not allocate AVFrame."<<endl;
        return -1;
    }
    frame->width=codec_ctx->width;
    frame->height=codec_ctx->height;
    frame->format=codec_ctx->pix_fmt;
    result= av_frame_get_buffer(frame,0);//给AVFrame结构中的音视频数据分配空间
    if(result<0){
        cerr<<"Error:could not get AVFrame buffer."<<endl;
        return -1;
    }
    return 0;
}
3.编码循环体 在编码循环体中,至少需要实现以下三个功能: 从视频源中循环获取输入图像 将当前帧传入编码器进行编码,获取输出的码流包 输出码流包中的压缩码流到输出文件

读取图像数据和写出码流数据:

点击查看代码
//io_data.cpp
int32_t read_yuv_to_frame(AVFrame* frame){
    int32_t frame_width=frame->width;
    int32_t frame_height=frame->height;
    int32_t luma_stride=frame->linesize[0];
    int32_t chroma_stride=frame->linesize[1];
    int32_t frame_size=frame_width*frame_height*3/2;
    int32_t read_size=0;
    if(frame_width==luma_stride){
        //如果width等于stride,则说明frame中不存在padding字节,可整体读取
        read_size+=fread(frame->data[0],1,frame_width*frame_height,input_file);
        read_size+=fread(frame->data[1],1,frame_width*frame_height/4,input_file);
        read_size+=fread(frame->data[2],1,frame_width*frame_height/4,input_file);
    }
    else{
        //如果width不等于stride,则说明frame中存在padding字节
        //对三个分量应该逐行读取
        for(size_t i=0;i<frame_height;i++){
            read_size+=fread(frame->data[0]+i*luma_stride,1,frame_width,input_file);
        }
        for(size_t uv=1;uv<=2;uv++){
            for(size_t i=0;i<frame_height/2;i++){
                read_size+=fread(frame->data[uv]+i*chroma_stride,1,frame_width/2,input_file);
            }
        }
    }
    if(read_size!=frame_size){
        cerr<<"Error:Read data error,frame_size:"<<frame_size<<",read_size:"<<read_size<<endl;
        return -1;
    }
    return 0;
}
void write_pkt_to_file(AVPacket* pkt){
    fwrite(pkt->data,1,pkt->size,output_file);
}
编码一帧图像数据:
点击查看代码
//video_encoder_core.cpp
static int32_t encode_frame(bool flushing){
    int32_t result=0;
    if(!flushing){
        cout<<"Send frame to encoder with pts:"<<frame->pts<<endl;
    }
    result=avcodec_send_frame(codec_ctx,flushing? nullptr:frame);
    if(result<0){
        cerr<<"Error:avcodec_send_frame failed."<<endl;
        return result;
    }
    while(result>=0){
        result= avcodec_receive_packet(codec_ctx,pkt);
        if(result==AVERROR(EAGAIN)||result==AVERROR_EOF){//尚未完成对新一帧的编码,要传入后续帧或编码器已完全输出内部缓存的码流
            return 1;
        }
        else if(result<0){
            cerr<<"Error:avcodec_receive_packet failed."<<endl;
            return result;
        }
        if(flushing){
            cout<<"Flushing:";
        }
        cout<<"Got encoded package with dts:"<<pkt->dts<<",pts:"<<pkt->pts<<", "<<endl;
        write_pkt_to_file(pkt);
    }
    return 0;
}
编码循环体的整体实现:
点击查看代码
//video_encoder_core.cpp
int32_t encoding(int32_t frame_cnt){
    int result=0;
    for(size_t i=0;i<frame_cnt;i++){
        result= av_frame_make_writable(frame);//确保AVFrame是可写的
        if(result<0){
            cerr<<"Error:could not av_frame_make_writable."<<endl;
            return result;
        }
        result= read_yuv_to_frame(frame);
        if(result<0){
            cerr<<"Error:read_yuv_to_frame failed."<<endl;
            return result;
        }
        frame->pts=i;
        result= encode_frame(false);
        if(result<0){
            cerr<<"Error:encode_frame failed."<<endl;
            return result;
        }
    }
    result= encode_frame(true);
    if(result<0){
        cerr<<"Error:flushing failed."<<endl;
        return result;
    }
    return 0;
}
关闭编码器:
点击查看代码
//video_encoder_core.cpp
void destroy_video_encoder(){
    avcodec_free_context(&codec_ctx);
    av_frame_free(&frame);
    av_packet_free(&pkt);
}
最终main函数的实现如下:
点击查看代码
int main(){
    const char* input_file_name= "../input.yuv";
    const char* output_file_name= "../output.h264";
    const char* codec_name= "libx264";
    int32_t result= open_input_output_files(input_file_name,output_file_name);
    if(result<0){
        return result;
    }
    result=init_video_encoder(codec_name);
    if(result<0){
        return result;
    }
    result=encoding(250);
    if(result<0){
        return result;
    }
    destroy_video_encoder();
    close_input_output_files();
    return 0;
}
执行完成后会生成输出视频码流文件output.h264,使用ffplay可以播放该文件,查看编码结果。

标签:码流,int32,libavcodec,frame,ctx,yuv,width,codec,size
From: https://www.cnblogs.com/luqman/p/libavcodec.html

相关文章

  • JavaCV音视频开发宝典:使用JavaCV读取海康平台或海康网络摄像头sdk回调视频TS码流并解
    《JavaCV音视频开发宝典》专栏目录导航《JavaCV音视频开发宝典》专栏介绍和目录​前言两年前博主写了如何利用JavaCV解析各种h264裸流,《JavaCV音视频开发宝典:使用javacv读取GB28181、海康大华平台和网络摄像头sdk回调视频码流并解析预览图像》,但是随着时间变化,各个厂商sdk也......
  • Android平台GB28181设备接入模块如何对接NV21、YV12、RGB、YUV等外部数据
    技术背景我们在对接Android平台GB28181设备接入模块的开发者时,遇到这样的场景,除了Android设备(如执法记录仪、智能安全帽等)自带的camera或camera2前后摄像头数据外,还有些场景是需要外部编码前或编码后数据,比如对接OTG类似的外置数据源,如NV12、NV21、YV12、RGB或YUV等格式,这里做个简......
  • Android平台GB28181设备接入模块如何实现实时视频和本地录像双码流编码
    ​技术背景我们在做Android平台GB28181设备接入模块的时候,遇到这样的场景,比如执法记录仪或智慧工地等场景下,由于GB28181设备接入模块,注册到国标平台后,平时只是心跳保持,或还有实时位置订阅,查看视频的时候,是按需看,而且有时候,网络环境并不是太好,所以,催生了这样一个诉求:部分开发者希......
  • DolohinScheduler 分布式任务调度框架 代码流程分解
    一、DS-API模块-执行工作流 -定时任务执行 更新schedule参数 -/schedule新增schedule参数做了什么事? 将schedule参数用ScheduleParam类进行解析 有效性校验,而后解析保存到t_ds_schedules表内,更新t_ds_process_definition表 -/onlin......
  • 免费码流分析软件YUView(转)
    原文:https://zhuanlan.zhihu.com/p/558580168作者:codec2021大家周末好,如果你是做视频编解码的研究或者开发工作,大概率离不开码流分析软件/工具。vq-analyzer和Elecard虽说功能很强大,但都是商用收费的,且价格并不便宜,还有各种Licence限制。今天我给推荐一个Github上开源,且跨平台......
  • mac:使用VLC播放纯视频YUV文件和纯音频pcm文件(命令行)
    一、使用vlc播放yuv有时候,我们需要播放一些纯视频文件,判断YUV数据是否可用。举个例子,我们使用命令行,播放/Users/domain/Desktop目录下的:test\_yuv420p\_320x180.yuv文件,命令如下:$/Applications/VLC.app/Contents/MacOS/VLC--demuxrawvideo--rawvid-fps15--rawvid-width320......
  • Qt编写网络摄像头推流(4路1080P主码流只占用0.2%CPU/极低延时极速响应)
    一、前言说明将从网络摄像头拉流过来的视频流重新推流出去,是目前一个很常规的做法,尤其是推流到流媒体服务中心,这样流媒体服务中心就把散落在各个区域的监控摄像头集中起来统一管理,同时提供对外一致的访问接口。很多时候不仅仅是几个摄像头,很可能是几百个上千个,所以对推流程序也是......
  • H264码流格式解析及RTP打包规则整理(转)
    原文链接:https://blog.csdn.net/luoyaxing0812/article/details/111352155版权声明:本文为CSDN博主「空谷_幽兰」的原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接及本声明。1、H264的结构图  2、H264的编码分层H.264原始码流(裸流)是由一个接一个NALU组......
  • SDL应用之YUV图像与音频输出
    1.YUV简介   YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素......
  • [linux][uvc]YUV格式编码的图片
    YUV格式编码的图片,在Linux下需要安装ffmpeg。#安装ffmpeg$sudoaptinstallffmpeg#查看图片,需要注意的是YUV图像的信息中并没有存储宽和高,所以在打开时需要指定图像......