ffmpeg转码视频文件

发布时间 2023-12-15 16:46:27作者: 林西索

转码

  • 转码视频文件例子
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