首页 > 其他分享 >ffmpeg视频编码

ffmpeg视频编码

时间:2024-11-15 23:44:56浏览次数:3  
标签:编码 编码器 pCodecCtx ffmpeg 视频 frame av false data

一、视频编码流程

使用ffmpeg解码视频帧主要可分为两大步骤:初始化编码器编码视频帧,以下代码以h264为例

1. 初始化编码器

初始化编码器包含以下步骤:

(1)查找编码器

videoCodec = avcodec_find_encoder_by_name(videoCodecName);
if (!videoCodec) {
    release();
    return false;
}

(2)设置编码器上下文参数

pCodecCtx = avcodec_alloc_context3(videoCodec);
pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准
pCodecCtx->framerate = { m_fps, 1 };
pCodecCtx->bit_rate = videoBitrate;
pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0); // 设置编码速度
// 以下为根据对应的编码格式设置相关的参数
if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
{
    pCodecCtx->keyint_min = m_fps;
    av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);
    av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);
    av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
}
else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
    pCodecCtx->max_b_frames = 2;
else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
    pCodecCtx->mb_decision = 2;

(3)打开编码器

if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联
{
    release();
    return false;
}

(4)设置图像帧参数

// 初始化编码帧
in_frame = av_frame_alloc();
if (!in_frame) {

    return false;
}
// 设置 AVFrame 的其他属性
in_frame->width = pCodecCtx->width;
in_frame->height = pCodecCtx->height;
in_frame->format = pCodecCtx->pix_fmt;

// 计算所需缓冲区大小
int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
in_frame_buf = (uint8_t*)av_malloc(size); // 分配图像帧内存空间
// 填充 AVFrame
av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);

(5)创建格式转换上下文
ffmpeg的libx264编码器只支持输入格式为P,如果输入格式不是YUV420P,则需要转换

// 创建缩放上下文,图像缩放和格式转换上下文
if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
    sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,
        in_w, in_h, AV_PIX_FMT_YUV420P,
        SWS_BILINEAR, NULL, NULL, NULL);
    if (!sws_ctx) {
        release();
        return false;
    }
}

(6)封装设置和打开文件
如果要将编码后数据进行封装(如封装成mp4文件),则需要使用AVFormatContext,并设置视频流

// 打开格式上下文
 if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {
    release();
    return false;
 }
  // 初始化视频码流
video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);
if (!video_st) {
    std::cout << "avformat_new_stream error" << endl;
    return false;
}
fmt = pFormatCtx->oformat;
video_st->id = pFormatCtx->nb_streams - 1;
avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);
pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;

// 打开文件
if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {
    //std::cout << "output file open fail!" << endl;
    swprintf(buf, 1024, L"avio_open error");
    logger.logg(buf);
    return false;
}
// 输出格式信息
av_dump_format(pFormatCtx, 0, mp4_file, 1);

2. 编码视频帧

(1)将编码数据送往解码器

    
    // data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧的大小
    // 如果输入数据格式不是YUV420P则需要转化
    if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
        if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {
            
            return false;
        }
    }
    else {
        memcpy(in_frame->data[0],  data, size);
    }

    in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号
   
    // 编码
    int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器
    if (ret < 0) {
        return false;
    }

(2)接收编码数据

AVPacket* vicdeo_pkt = av_packet_alloc();
while (ret >= 0) {
    ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        break;
    else if (ret < 0) {
        //cout << "Error during encoding" << endl;
        return false;
    }

    // Prepare packet for muxing
    pkt.stream_index = video_st->index;
    av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基
    pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。

    ret = av_interleaved_write_frame(pFormatCtx, &pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中
    if (ret < 0) {
        
        return false;
    }
    
    // Free the packet
    av_packet_unref(&pkt);
}
++videoPktCount; // 每编码完成一帧,videoPktCount加一
av_packet_free(&vicdeo_pkt);
videoPkt = nullptr;

二、使用ffmpeg实现对内存中的视频帧数据编码

以下代码实现了ffmpeg对视频流数据进行编码的主要过程,可分为初始化编码器(InitEncoder)和编码视频帧(DecodeVideoFrame)


extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/error.h>
}

bool mHasVideo = false;
char* mp4_file = "test.mp4";
int in_w = 1920;
int in_h = 1080;
int m_fps = 30;
int64_t videoBitrate = 6000000;
int      mVideoSrcChannel = 0;
int      mVideoSrcStride[1] = { 0 };
int64_t   videoPktCount = 0;

AVPixelFormat videoSrcFormat = AV_PIX_FMT_YUV420P; // 输入的像素格式

AVStream* video_st = nullptr;
const AVCodec* videoCodec = nullptr; // 视频编码器
AVCodecContext* pCodecCtx = nullptr; // 视频编码器上下文

uint8_t* in_frame_buf = nullptr;
AVFrame* in_frame = nullptr;
SwsContext* sws_ctx = nullptr;

// 初始化编码器
bool InitEncoder() {
     /****   编码器设置      ****/
    // 查找编码器
    videoCodec = avcodec_find_encoder_by_name(videoCodecName);
    if (!videoCodec) {
        release();
        return false;
    }
    pCodecCtx = avcodec_alloc_context3(videoCodec);
    pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准
    pCodecCtx->framerate = { m_fps, 1 };
    pCodecCtx->bit_rate = videoBitrate;
    pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响
    pCodecCtx->width = in_w;
    pCodecCtx->height = in_h;
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0);

    // 以下为根据对应的编码格式设置相关的参数
    if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
    {
        pCodecCtx->keyint_min = m_fps;
        av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);
        av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);
        av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
    }
    else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
        pCodecCtx->max_b_frames = 2;
    else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
        pCodecCtx->mb_decision = 2;

    if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联
    {
        release();
        return false;
    }

    /****   封装设置      ****/
    if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {
        swprintf(buf, 1024, L"avformat_alloc_output_context2 error");
        logger.logg(buf);
        return false;
    }
    
    // 初始化视频码流
    video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);
    if (!video_st) {
        std::cout << "avformat_new_stream error" << endl;
        return false;
    }
    video_st->id = pFormatCtx->nb_streams - 1;
    avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);
    pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;
    

    // 打开文件
    if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {
        release();
        return false;
    }
    // 输出格式信息
    av_dump_format(pFormatCtx, 0, mp4_file, 1);
    
    /****** 输入参数 *********/
    // 初始化编码帧
    in_frame = av_frame_alloc();
    if (!in_frame) {

        return false;
    }
    // 设置 AVFrame 的其他属性
    in_frame->width = pCodecCtx->width;
    in_frame->height = pCodecCtx->height;
    in_frame->format = pCodecCtx->pix_fmt;

    // 计算所需缓冲区大小
    int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
    in_frame_buf = (uint8_t*)av_malloc(size);
    // 填充 AVFrame
    av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);


    // 创建缩放上下文,图像缩放和格式转换上下文
    if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
        sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,
            in_w, in_h, AV_PIX_FMT_YUV420P,
            SWS_BILINEAR, NULL, NULL, NULL);
        if (!sws_ctx) {
            release();
            return false;
        }
    }

    av_image_fill_linesizes(mVideoSrcStride, videoSrcFormat, in_w); // 计算图像的行大小,格式转换时会用上
    
    videoPktCount = 0;
    
    

}

bool DecodeVideoFrame(uint8_t* data, int size) {

    // data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧大小
    // 如果输入数据格式不是YUV420P则需要转化
    if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
        if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {
            
            return false;
        }
    }
    else {
        memcpy(in_frame->data[0],  data, size);
    }

    in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号
   
    // 编码
    int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器
    if (ret < 0) {
        return false;
    }

    AVPacket* vicdeo_pkt = av_packet_alloc();
    while (ret >= 0) {
        ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0) {
            
            return false;
        }

        // Prepare packet for muxing
        vicdeo_pkt.stream_index = video_st->index;
        av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基
        vicdeo_pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。

        ret = av_interleaved_write_frame(pFormatCtx, vicdeo_pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中
        if (ret < 0) {
            
            return false;
        }
        
        // Free the packet
        av_packet_unref(vicdeo_pkt);
        
    }
    av_packet_free(&vicdeo_pkt);
    videoPkt = nullptr;
    ++videoPktCount; // 每编码完成一帧,videoPktCount加一
}

void release() {
    
    if (in_frame_buf) {
        av_free(in_frame_buf);
        in_frame_buf = nullptr;
    }
    if (in_frame) {
        av_frame_free(&in_frame);
        in_frame = nullptr;
    }
   
    if (pCodecCtx) {
        avcodec_close(pCodecCtx);
        avcodec_free_context(&pCodecCtx);
        pCodecCtx = nullptr;

    }
    if (sws_ctx) {
        sws_freeContext(sws_ctx);
        sws_ctx = nullptr;
    }

    if (pFormatCtx) {
        avio_close(pFormatCtx->pb);
        avformat_free_context(pFormatCtx);
        pFormatCtx = nullptr;
    }
}
    

标签:编码,编码器,pCodecCtx,ffmpeg,视频,frame,av,false,data
From: https://blog.csdn.net/qq_51699436/article/details/143810037

相关文章

  • 音视频基础能力之 iOS 视频篇(一):视频采集
    涉及硬件的音视频能力,比如采集、渲染、硬件编码、硬件解码,通常是与客户端操作系统强相关的,就算是跨平台的多媒体框架也必须使用平台原生语言的模块来支持这些功能本系列文章将详细讲述移动端音视频的采集、渲染、硬件编码、硬件解码这些涉及硬件的能力该如何实现本文为该系......
  • 视频编码基础入门
    文章目录前言一、视频编码的目标二、视频编码基本流程1.采样与颜色空间转换2.变换编码(例如DCT)3.量化4.熵编码5.运动补偿和帧间预测6.编码输出三、视频编码的关键技术1.帧类型2.GOP(GroupofPictures)结构3.比特率控制四、常见的视频编码标准H.264(AVC)H.265......
  • 基于python+django的Hadoop的短视频数据分析的设计与实现
    前言基于python+django的Hadoop短视频数据分析系统可充分挖掘短视频数据价值。从各大短视频平台接口等多种数据源采集数据,利用Hadoop分布式存储海量短视频的基本信息、用户信息、播放量、点赞数、评论内容等。借助python数据分析库和django框架,清洗、预处理......
  • 杭州市公安局打造“清风明月说反诈”系列视频,守好市民“钱袋子”
    为进一步加强辖区群众的反诈防骗意识,提升反诈宣传效率。杭州市公安局推出“清风明月说反诈”系栏目,借助有言的AIGC视频生成能力,高效率制作多场景的反诈宣传视频,升级传统视频制作方式,展示了AI技术在反诈宣传科普以及公共安全社会治理中的巨大价值。没有使用AI视频工具之前,传统......
  • 基于米尔NXP i.MX93开发板OpenCV的相机捕捉视频进行人脸检测
    本篇测评由优秀测评者“eefocus_3914144”提供。 本文将介绍基于米尔电子MYD-LMX93开发板(米尔基于NXPi.MX93开发板)的基于OpenCV的人脸检测方案测试。OpenCV提供了一个非常简单的接口,用于相机捕捉一个视频(我用的电脑内置摄像头)1、安装python3-opencvaptinstallpython3-......
  • NVR接入录像回放平台EasyCVR视频设备轨迹回放平台分发webrtc流地址无法播放是什么原因
    在现代安防领域,视频监控技术扮演着越来越重要的角色。EasyCVR视频汇聚平台以其卓越的兼容性和灵活性,为用户提供了一个强大的视频监控解决方案。该平台不仅能够满足基本的视频监控需求,还通过一系列高级功能,如视频转码、快照、告警处理等,极大地提升了监控系统的智能化水平和操作便捷......
  • 无插件H5播放器EasyPlayer.js网页web无插件播放器选择全屏时,视频区域并没有全屏问题的
    EasyPlayer.jsH5播放器,是一款能够同时支持HTTP、HTTP-FLV、HLS(m3u8)、WS、WEBRTC、FMP4视频直播与视频点播等多种协议,支持H.264、H.265、AAC、G711A、MP3等多种音视频编码格式,支持MSE、WASM、WebCodec等多种解码方式,支持Windows、Linux、Android、iOS全平台终端的H5播放器,使用简单......
  • 网页直播/点播播放器EasyPlayer.js RTSP播放器出现多路视频卡顿、内存开始飙升的原因
    EasyPlayer.jsRTSP播放器是TSINGSEE青犀流媒体组件系列中关注度较高的产品,经过多年的发展和迭代,目前已经有多个应用版本,包括RTSP版、RTMP版、Pro版以及js版,其中js版本作为网页播放器,受到了用户的广泛使用。1、问题说明在已经使用硬解码基础上,播放多路视频,会出现卡顿,内存开始飙......
  • 基于yolov10的柿子成熟度检测系统,支持图像、视频和摄像实时检测【pytorch框架、python
     更多目标检测和图像分类识别项目可看我主页其他文章功能演示:yolov10,柿子成熟度检测系统,支持图像、视频和摄像实时检测【pytorch框架、python】_哔哩哔哩_bilibili(一)简介基于yolov10的柿子成熟度检测系统是在pytorch框架下实现的,这是一个完整的项目,包括代码,数据集,训练好的......
  • 国标GB28181软件LiteGBS国标GB28181公网直播流媒体服务与视频监控直播的解决方案
    LiteGBS国标GB28181-2022平台具有诸多显著特点。它全力支持设备以GB28181协议接入,这意味着能够广泛接纳符合该协议标准的各类设备,为用户提供了丰富的设备选择空间。接入设备后,LiteGBS可对视频进行高效地解码、处理和分发等服务,确保视频信号的稳定传输和高质量呈现。在现代视频监......