首页 > 其他分享 >ffmpeg转码视频文件

ffmpeg转码视频文件

时间:2023-12-15 16:56:47浏览次数:26  
标签:return ffmpeg 转码 frame ctx ret av 视频文件 NULL

转码

  • 转码视频文件例子
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
}

AVFormatContext *ifmt_ctx = nullptr;
AVFormatContext *ofmt_ctx = nullptr;
AVCodecContext *decodec_ctx = nullptr;
AVCodecContext *encodec_ctx = nullptr;
AVPacket *packet = nullptr;
AVFrame *dec_frame = nullptr;
AVPacket *enc_pkt = nullptr;
int videoStreamIndex = -1;
int ret = 0;

int openInfile(const char *infile);
int openOutfile(const char *outfile);
int decode_encode();
int encode(AVFrame *frame, AVPacket *enc_pkt);
void release();

int simpleTranscodeStart(const char *infile, const char *outfile)
{
    if (0 != openInfile(infile))
    {
        return -1;
    }

    if (0 != openOutfile(outfile))
    {
        return -1;
    }

    if (0 != decode_encode())
    {
        return -1;
    }

    //写入文件尾
    av_write_trailer(ofmt_ctx);

    //释放资源
    release();

    return 0;
}

int openInfile(const char *infile)
{
    unsigned int i;

    //打开视音频文件,视音频码流的详细参数会保存在 ifmt_ctx 中。此接口也可用作拉流
    //AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体
    if ((ret = avformat_open_input(&ifmt_ctx, infile, NULL, NULL)) < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
        return ret;
    }

    //该函数读取一部分视音频数据并且获得一些相关的信息,给每个媒体流(音频/视频)的AVStream结构体赋值
    if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
        return ret;
    }

    //nb_streams表示打开的音视频文件中的流数量,一般音视频文件中会包含视频流,音频流,字幕流。流的信息用AVStream结构体保存
    //从AVStream中有一个变量AVCodecParameters *codecpar,可以通过此变量来打开对应的解码器
    for (i = 0; i < ifmt_ctx->nb_streams; i++)
    {
        AVStream *stream = ifmt_ctx->streams[i];
        //只解码音频
        if (stream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
        {
            continue;
        }

        videoStreamIndex = i;

        //根据id查找解码器。还可以用avcodec_find_decoder_by_name()根据解码器名字查找解码器
        //编译ffmpeg时决定了有多少可用的解码器
        const AVCodec *dec = avcodec_find_decoder(stream->codecpar->codec_id);
        if (!dec)
        {
            av_log(NULL, AV_LOG_ERROR, "Failed to find decoder for stream #%u\n", i);
            return AVERROR_DECODER_NOT_FOUND;
        }

        //avcodec_alloc_context3()初始化一个解码器上下文。需要用avcodec_free_context()释放
        //AVCodecContext 编解码器上下文,解码时需要传入这个作为参数
        //音频和视频的编码解码,都是用这个结构体来保存编解码所需的参数。初始化时参数必须设置正确
        decodec_ctx = avcodec_alloc_context3(dec);
        if (!decodec_ctx)
        {
            av_log(NULL, AV_LOG_ERROR, "Failed to allocate the decoder context for stream #%u\n", i);
            return AVERROR(ENOMEM);
        }

        //把AVStream中的编码器结构复制到解码器context中
        ret = avcodec_parameters_to_context(decodec_ctx, stream->codecpar);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "Failed to copy decoder parameters to input decoder context for stream #%u\n", i);
            return ret;
        }

        //把流的时间基赋值给解码器
        //时间基和时间戳在播放和封装时很重要。时间基表示单位,时间戳表示时间。时间戳 * 时间基 = 实际时间(s)
        decodec_ctx->pkt_timebase = stream->time_base;

        /* Reencode video & audio and remux subtitles etc. */
        decodec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, stream, NULL);

        //打开解码器
        ret = avcodec_open2(decodec_ctx, dec, NULL);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", i);
            return ret;
        }
    } //for nb_streams

    //打印输入视频文件的信息
    av_dump_format(ifmt_ctx, 0, infile, 0);

    return 0;
}

int openOutfile(const char *outfile)
{
    AVStream *out_stream;
    AVStream *in_stream;
    const AVCodec *encoder;
    int ret;
    unsigned int i;

    ofmt_ctx = NULL;
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, outfile);
    if (!ofmt_ctx)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not create output context\n");
        return AVERROR_UNKNOWN;
    }

    // for (i = 0; i < ifmt_ctx->nb_streams; i++)
    {
        //添加一个输出流
        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream)
        {
            av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n");
            return AVERROR_UNKNOWN;
        }

        in_stream = ifmt_ctx->streams[videoStreamIndex];

        //查找编码器。原视频编码为x264,编码为AV_CODEC_ID_MJPEG
        encoder = avcodec_find_encoder(AV_CODEC_ID_MJPEG /*decodec_ctx->codec_id*/);
        if (!encoder)
        {
            av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n");
            return AVERROR_INVALIDDATA;
        }

        //创建编码器Context
        encodec_ctx = avcodec_alloc_context3(encoder);
        if (!encodec_ctx)
        {
            av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n");
            return AVERROR(ENOMEM);
        }

        //编码器参数设置,这里除了编码格式,其它编码的参数都是从解码器复制过来
        //实际使用时,编码器参数需要根据需求进行设置
        if (encodec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            encodec_ctx->height = decodec_ctx->height;
            encodec_ctx->width = decodec_ctx->width;
            encodec_ctx->sample_aspect_ratio = decodec_ctx->sample_aspect_ratio;
            /* take first format from list of supported formats */
            if (encoder->pix_fmts)
                encodec_ctx->pix_fmt = encoder->pix_fmts[0];
            else
                encodec_ctx->pix_fmt = decodec_ctx->pix_fmt;
            /* video time_base can be set to whatever is handy and supported by encoder */
            encodec_ctx->time_base = av_inv_q(decodec_ctx->framerate);
        }

        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            encodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

        /* Third parameter can be used to pass settings to encoder */
        ret = avcodec_open2(encodec_ctx, encoder, NULL);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream \n");
            return ret;
        }
        //把编码器中的信息复制到视频流中
        ret = avcodec_parameters_from_context(out_stream->codecpar, encodec_ctx);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream\n");
            return ret;
        }

        out_stream->time_base = encodec_ctx->time_base;
    }
    av_dump_format(ofmt_ctx, 0, outfile, 1);

    if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
    {
        //打开AVIOContext
        //AVIOContext是ffmpeg中管理输入输出数据的结构体
        ret = avio_open(&ofmt_ctx->pb, outfile, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", outfile);
            return ret;
        }
    }

    //写入文件头
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n");
        return ret;
    }

    return 0;
}

int decode_encode()
{
    packet = av_packet_alloc();
    dec_frame = av_frame_alloc();
    enc_pkt = av_packet_alloc();
    if (!packet || !dec_frame || !enc_pkt)
    {
        return -1;
    }

    while (1)
    {
        //从输入文件(流)读取一帧数据,保存在AVPacket中
        //AVPacket是存储压缩编码数据相关信息的结构体
        if ((ret = av_read_frame(ifmt_ctx, packet)) < 0)
        {
            break;
        }

        if (packet->stream_index != videoStreamIndex)
        {
            av_packet_unref(packet);
            continue;
        }

        //向解码器发送一帧需要解码的数据
        //解码器不是喂入一帧数据就解码出一帧数据,可能是放入多帧才解码出来一帧数据,也可能一次出来多帧数据
        ret = avcodec_send_packet(decodec_ctx, packet);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");
            break;
        }

        while (ret >= 0)
        {
            //尝试从解码器拿一帧解码后的数据AVFrame
            //AVFrame保存解码后的图像数据(比如视频YUV,RGB;音频PCM)和一些相关的信息,如pts,dts,duration等
            ret = avcodec_receive_frame(decodec_ctx, dec_frame);
            //AVERROR_EOF:文件结束。AVERROR(EAGAIN):需要喂更多AVPacket才能解码出一帧
            if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
            {
                break;
            }
            else if (ret < 0)
            {
                return ret;
            }

            dec_frame->pts = dec_frame->best_effort_timestamp;
            ret = encode(dec_frame, enc_pkt);
            if (ret < 0)
            {
                return ret;
            }
        }
        av_packet_unref(packet);
    } //while(1)

    //冲洗解码器,向解码器发一个空包,即表示flush
    ret = avcodec_send_packet(decodec_ctx, NULL);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Flushing decoding failed\n");
        return -1;
    }

    while (ret >= 0)
    {
        ret = avcodec_receive_frame(decodec_ctx, dec_frame);
        if (ret == AVERROR_EOF)
            break;
        else if (ret < 0)
            return ret;

        dec_frame->pts = dec_frame->best_effort_timestamp;
        ret = encode(dec_frame, enc_pkt);
        if (ret < 0)
            return ret;
    }

    //冲洗编码器。向编码器发送一个空包
    ret = encode(NULL, enc_pkt);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Flushing encoder failed\n");
        return ret;
    }

    return 0;
}

int encode(AVFrame *frame, AVPacket *enc_pkt)
{
    av_packet_unref(enc_pkt);

    //向编码器放入一帧AVFrame
    //类似于解码器,不是放入一帧就会编码出来一帧
    ret = avcodec_send_frame(encodec_ctx, frame);
    if (ret < 0)
        return ret;

    while (ret >= 0)
    {
        //从编码器拿一帧编码后的数据
        ret = avcodec_receive_packet(encodec_ctx, enc_pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return 0;

        //这里需要设置帧的时间戳,时间基等参数,否则封装后的视频文件可能不会正常播放
        enc_pkt->stream_index = videoStreamIndex;
        enc_pkt->time_base = AVRational{1, 1000};

        //av_packet_rescale_ts(enc_pkt, encodec_ctx->time_base, ofmt_ctx->streams[videoStreamIndex]->time_base);

        av_log(NULL, AV_LOG_DEBUG, "Muxing frame\n");
        /* mux encoded frame */
        if (frame)
        {
            enc_pkt->duration = frame->duration;
        }
        //把编码后的帧输出
        ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt);
        if (ret < 0)
            return ret;
    }

    return 0;
}

void release()
{
    if (ifmt_ctx)
        avformat_free_context(ifmt_ctx);
    if (ofmt_ctx)
        avformat_free_context(ofmt_ctx);

    if (decodec_ctx)
        avcodec_free_context(&decodec_ctx);
    if (encodec_ctx)
        avcodec_free_context(&encodec_ctx);

    if (packet)
        av_packet_free(&packet);
    if (dec_frame)
        av_frame_free(&dec_frame);
    if (enc_pkt)
        av_packet_free(&enc_pkt);
}

** 几个流程中,主要的几个结构体 **

  • 1.解封装和封装,主要用到AVFormatContext。在AVFormatContext中的AVStream结构,保存流的信息,主要是编码信息

  • 2.解码和编码,主要用到AVCodecContext

  • 3.解码后的数据保存AVFrame,编码后的数据保存AVPacket

标签:return,ffmpeg,转码,frame,ctx,ret,av,视频文件,NULL
From: https://www.cnblogs.com/linxisuo/p/17903688.html

相关文章

  • ffmpeg 添加自定义编解码插件
    有两种方法:一.ffmpeg添加自定义编解码插件(以修改ffmpeg源码的方式添加)例:添加一个解码器,给这个解码器取个名字叫mydecoder,可以通过下面的步骤添加:1.在libavcodec目录下,新建文件mydecoder.c#include"avcodec.h"#include"codec_internal.h"//自己封装的编解码器的头文件#......
  • 【ffmpeg】FFmpeg命令行
    【参考链接】FFmpeg命令行教程:66个实用案例解析常见FFmpeg命令行全面分析FFmpeg最常用命令参数详解及应用实例......
  • Qt/C++视频监控安卓版/多通道显示视频画面/录像存储/视频播放安卓版/ffmpeg安卓
    一、前言随着监控行业的发展,越来越多的用户场景是需要在手机上查看监控,而之前主要的监控系统都是在PC端,毕竟PC端屏幕大,能够看到的画面多,解码性能也强劲。早期的手机估计性能弱鸡,而现在的手机性能不是一般的牛,甚至超越了PC机的性能,所以手机上查看多路监控也就有了硬件基础前提。对......
  • 怎么在Android项目中导入ffmpeg库?
    1.前言在这里我以导入静态库(.a)为例进行分析,动态库(.so)是类似的。在导入前,各位要先编译好ffmpeg库,需要注意的是在编译的时候要开启交叉编译,目标平台为Android,其他平台的库(windows,linux)在Android平台使用不了,我这里编译的是armeabi-v7a架构的库。2.步骤(1)新建一......
  • ts视频文件批量下载与合并
    importrequestsimportosimporttime#定义下载函数defdownload_resource(url,filename):#设置最大重试次数max_retries=3retries=0whileretries<max_retries:try:response=requests.get(url)ifrespo......
  • ffmpeg
     ffmpeg-itest.m4a-y-acodeclibmp3lame-aq0"test.mp3"这是一个使用ffmpeg命令行工具将test.m4a文件转换为test.mp3文件的命令。具体解释如下:-itest.m4a:指定输入文件为test.m4a。-y:覆盖输出文件,如果已存在同名文件,则自动覆盖。-acodeclibmp3lame:设置音频编码......
  • java使用Ffmpeg合成音频和视频
    1、Maven依赖<!--需要注意,javacv主要是一组API为主,还需要加入对应的实现--><dependency><groupId>org.bytedeco</groupId><artifactId>javacv</artifactId><version>1.5.6</version>&......
  • js 中的 base64 转码 btoa/atob
    1场景有时,文本里面包含一些不可打印的符号,比如ASCII码0到31的符号都无法打印出来,这时可以使用Base64编码,将它们转成可以打印的字符。另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用Base64编码。所谓Base64就是一种编码方法,可以将任意值转成0~9、A~Z、a-z......
  • 如何在mac上安装ffmpeg
    有三种方法在mac上安装ffmpeg使用包管理工具Homebrew或MacPorts来安装ffmpeg。这种安装方式可以自动保持最新版本。详细步骤见下文下载编译好的二进制可执行文件。这种安装方式最容易。直接下载就可用。缺点是必须手动更新。并且它可能没有包含所有你需要的编码器和滤镜。自己......
  • 音视频 ffmpeg
    音频和视频是多媒体领域的两个主要组成部分,它们在娱乐、通信、广播、教育等各个领域中起着重要作用。下面是对音频和视频的详细解释:音频:音频是通过振动传播的声音信号。它可以是人类语音、音乐、环境声音等。以下是一些音频相关的概念和技术:采样率(SampleRate):音频信号是连续......