【FFmpeg系列】FFmpeg解码视频文件为YUV

1 篇文章 0 订阅

音视频解码流程

音视频解码流程如下如所示:
在这里插入图片描述

FFmpeg视频解码流程

在这里插入图片描述

主要步骤解析

  • 打开并读取输入文件
    int ret = 0;
    pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, inputPath, nullptr, nullptr) < 0) {
        LOGE("decoder decode avformat_open_input failed.");
        releaseDecoder();
        return -1;
    }

这一步将会打开输入文件,并读取文件头信息,但是并不会打开解码器。

  • 读取流信息
    if (avformat_find_stream_info(pFormatCtx, nullptr) < 0) {
        LOGE("decoder decode avformat_find_stream_info failed.");
        releaseDecoder();
        return -1;
    }

这一步将会读取输入文件的几个packet来得到文件的流信息。当然,这个方法并不会改变文件的逻辑位置,所以之后可以放心的读取文件内容而不用担心读取位置不从头开始。

  • 找到对应的流
    ret = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (ret < 0) {
        LOGE("decoder decode av_find_best_stream failed.");
        releaseDecoder();
        return -1;
    }
    mVideoStreamIndex = ret;
    pVideoStream = pFormatCtx->streams[mVideoStreamIndex];

该方法将会根据传入的类型(比如视频流、音频流、字幕流等) 来寻找最佳的流信息,如果寻找成功,返回流的大于零的number,否则返回负数错误码。

  • 找到对应的解码器
    pVideoDecoder = avcodec_find_decoder(pVideoStream->codecpar->codec_id);
    if (!pVideoDecoder) {
        LOGE("decoder decode avcodec_find_decoder failed.");
        releaseDecoder();
        return -1;
    }

这个方法可以根据传入的codec_id 也就是解码器的id来寻找对应的解码器。这里传入的是对应的流中指定的解码器id。当然,也可以使用解码器名字来寻找指定解码器:

pVideoDecoder = avcodec_find_decoder_by_name("解码器名字");
  • 打开解码器
    ret = avcodec_open2(pDecodeCtx, pVideoDecoder, nullptr);
    if (ret < 0) {
        LOGE("decoder decode avcodec_open2 failed.");
        releaseDecoder();
        return -1;
    }

该方法将会初始化解码器上下文以使对应的解码器可用。

  • 循环读取压缩数据
    while (av_read_frame(pFormatCtx, pPacket) >= 0) {
        // 解码操作
    }

该方法将会读取下一帧数据(压缩数据,这里也以帧来形容)。对于视频,读取的一个Packet中只有一个Frame数据。对于音频,一个Packet中可能含有一个或多个Frame的数据。如果音频帧格式是固定大小的(比如PCM 或者ADPCM数据),那么有可能含有多个Frame的数据。如果音频帧格式是可变大小的(比如MPEG音频数据),那么一个Packet只含一个Frame。返回负数表示出错或者读到EOF。

  • 将数据送给解码器
    int ret = 0;
    ret = avcodec_send_packet(pDecodeCtx, packet);
    if (ret < 0 && (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)) {
        return -1;
    }

该函数将Packet送给解码器,第二个参数packet可以为NULL,为空表示解码器输入端EOS,也就是说数据都输入完了,没有数据给解码器了。
返回值为0:表示发送成功,
返回值为VERROR(EAGAIN):表示当前状态不接受输入,需要调用vcodec_receive_frame()取走解码后的数据。
返回AVERROR_EOF:表示解码器已经被flushed了,也就是解码器认为输入结束了。如上面所说,如果第二个参数传了null,标志输入结束,那么之后再次调用该方法就会接收到这个返回值。
返回其他负数表示发送失败。

  • 接收解码后的数据
    ret = avcodec_receive_frame(pDecodeCtx, pFrame);

该函数将会获取解码后的数据。
返回0:表示成功
返回AVERROR(EAGAIN):表示没有输出了,需要更多输入。
返回AVERROR_EOF:表示没有输出了,解码结束了。
返回其他负数表示失败。

举个栗子 (Talk is cheap. Show me the code)

我们这里封装了一个叫VideoDecoder的类,方便调用
头文件VideoDecoder.h

#ifndef TMEDIADEMO_VIDEODECODER_H
#define TMEDIADEMO_VIDEODECODER_H

#include <string>

extern "C" {
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};

class VideoDecoder {
public:
    VideoDecoder();

    ~VideoDecoder();

    int decode(const char *inputPath, const char *outputPath);

    void releaseDecoder();

private:
    AVFormatContext *pFormatCtx;
    int mVideoStreamIndex;
    AVStream *pVideoStream;
    AVCodec *pVideoDecoder;
    AVCodecContext *pDecodeCtx;
    AVFrame *pFrame;
    AVFrame *pOutFrame;
    AVPacket *pPacket;
    SwsContext *pSwsCtx;
    FILE *yuvFile;


    int decodeInner(AVPacket *packet, int width, int height);
};
#endif //TMEDIADEMO_VIDEODECODER_H

VideoDecoder.c

#include "VideoDecoder.h"
#include "logger.h"

extern "C" {
#include "libavutil/imgutils.h"
}

VideoDecoder::VideoDecoder() : pFormatCtx(nullptr), mVideoStreamIndex(0), pVideoStream(nullptr),
                               pVideoDecoder(nullptr), pDecodeCtx(nullptr), pFrame(nullptr), pOutFrame(nullptr),
                               pPacket(nullptr), pSwsCtx(nullptr), yuvFile(nullptr) {

}

VideoDecoder::~VideoDecoder() {

}

int VideoDecoder::decode(const char *inputPath, const char *outputPath) {
    if (!inputPath || !outputPath) {
        LOGE("decoder decode failed, path invalid.");
        return -1;
    }
    LOGI("decoder inputPath = %s\noutputPath = %s", inputPath, outputPath);
    int ret = 0;
    pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, inputPath, nullptr, nullptr) < 0) {
        LOGE("decoder decode avformat_open_input failed.");
        releaseDecoder();
        return -1;
    }
    if (avformat_find_stream_info(pFormatCtx, nullptr) < 0) {
        LOGE("decoder decode avformat_find_stream_info failed.");
        releaseDecoder();
        return -1;
    }
    ret = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (ret < 0) {
        LOGE("decoder decode av_find_best_stream failed.");
        releaseDecoder();
        return -1;
    }
    mVideoStreamIndex = ret;
    pVideoStream = pFormatCtx->streams[mVideoStreamIndex];
    pVideoDecoder = avcodec_find_decoder(pVideoStream->codecpar->codec_id);
    if (!pVideoDecoder) {
        LOGE("decoder decode avcodec_find_decoder failed.");
        releaseDecoder();
        return -1;
    }
    pDecodeCtx = avcodec_alloc_context3(pVideoDecoder);
    if (!pDecodeCtx) {
        LOGE("decoder decode avcodec_alloc_context3 failed.");
        releaseDecoder();
        return -1;
    }
    ret = avcodec_parameters_to_context(pDecodeCtx, pVideoStream->codecpar);
    if (ret < 0) {
        LOGE("decoder decode avcodec_parameters_to_context failed.");
        releaseDecoder();
        return -1;
    }
    ret = avcodec_open2(pDecodeCtx, pVideoDecoder, nullptr);
    if (ret < 0) {
        LOGE("decoder decode avcodec_open2 failed.");
        releaseDecoder();
        return -1;
    }
    pFrame = av_frame_alloc();
    pOutFrame = av_frame_alloc();
    int width = pDecodeCtx->width;
    int height = pDecodeCtx->height;
    if (width <= 0 || height <= 0) {
        LOGE("decoder decode width <= 0 || height <= 0.");
        releaseDecoder();
        return -1;
    }
    LOGI("video width =%d, height = %d", width, height);
    ret = av_image_alloc(pFrame->data, pFrame->linesize, width, height, pDecodeCtx->pix_fmt, 1);
    if (ret < 0) {
        LOGE("decoder decode av_image_alloc pFrame failed.");
        releaseDecoder();
        return -1;
    }
    ret = av_image_alloc(pOutFrame->data, pOutFrame->linesize, width, height, AV_PIX_FMT_YUV420P, 1);
    if (ret < 0) {
        LOGE("decoder decode av_image_alloc pOutFrame failed.");
        releaseDecoder();
        return -1;
    }
    pPacket = av_packet_alloc();
    pSwsCtx = sws_getContext(width, height, pDecodeCtx->pix_fmt,
                             width, height, AV_PIX_FMT_YUV420P,
                             SWS_BICUBIC, nullptr, nullptr, nullptr);
    yuvFile = fopen(outputPath, "wb+");
    if (!yuvFile) {
        LOGE("decoder decode fopen %s failed.", outputPath);
        releaseDecoder();
        return -1;
    }
    while (av_read_frame(pFormatCtx, pPacket) >= 0) {
        decodeInner(pPacket, width, height);
    }
    decodeInner(nullptr, width, height);
    fclose(yuvFile);
    releaseDecoder();
    LOGI("Decode video to yuv finished.");
    return 0;
}

int VideoDecoder::decodeInner(AVPacket *packet, int width, int height) {
    int ret = 0;
    ret = avcodec_send_packet(pDecodeCtx, packet);
    if (ret < 0 && (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)) {
        return -1;
    }
    ret = avcodec_receive_frame(pDecodeCtx, pFrame);
    if (ret < 0) {
        return -1;
    }
    int h = sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, pDecodeCtx->height, pOutFrame->data,
                      pOutFrame->linesize);
    fwrite(pOutFrame->data[0], 1, width * h, yuvFile);
    fwrite(pOutFrame->data[1], 1, width * h / 4, yuvFile);
    fwrite(pOutFrame->data[2], 1, width * h / 4, yuvFile);
    return 0;
}

void VideoDecoder::releaseDecoder() {
    if (pFrame) {
        av_frame_free(&pFrame);
    }
    if (pOutFrame) {
        av_frame_free(&pOutFrame);
    }
    if (pSwsCtx) {
        sws_freeContext(pSwsCtx);
        pSwsCtx = nullptr;
    }
    if (pDecodeCtx) {
        avcodec_free_context(&pDecodeCtx);
        pDecodeCtx = nullptr;
    }
    if (pFormatCtx) {
        avformat_close_input(&pFormatCtx);
        avformat_free_context(pFormatCtx);
        pFormatCtx = nullptr;
    }
}

最后

整个流程比较简单,基本在上面都讲到了。
可以去Github看完整项目:项目地址
如果喜欢的话别忘了Start,有任何问题欢迎Issue或者留言。

以下是使用c语言和FFmpeg解码视频为yuv420p格式并保存文件的代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <libavutil/imgutils.h> #include <libavutil/parseutils.h> #include <libavutil/opt.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> int main(int argc, char *argv[]) { AVFormatContext *fmt_ctx = NULL; int ret; if (argc < 2) { fprintf(stderr, "Usage: %s <input file>\n", argv[0]); exit(1); } ret = avformat_open_input(&fmt_ctx, argv[1], NULL, NULL); if (ret < 0) { fprintf(stderr, "Could not open input file '%s'\n", argv[1]); exit(1); } ret = avformat_find_stream_info(fmt_ctx, NULL); if (ret < 0) { fprintf(stderr, "Could not find stream information\n"); exit(1); } int video_stream_index = -1; for (int i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; } } if (video_stream_index == -1) { fprintf(stderr, "Could not find video stream\n"); exit(1); } AVCodecParameters *codecpar = fmt_ctx->streams[video_stream_index]->codecpar; AVCodec *codec = avcodec_find_decoder(codecpar->codec_id); if (!codec) { fprintf(stderr, "Codec not found\n"); exit(1); } AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { fprintf(stderr, "Could not allocate codec context\n"); exit(1); } ret = avcodec_parameters_to_context(codec_ctx, codecpar); if (ret < 0) { fprintf(stderr, "Could not copy codec parameters to codec context\n"); exit(1); } ret = avcodec_open2(codec_ctx, codec, NULL); if (ret < 0) { fprintf(stderr, "Could not open codec\n"); exit(1); } AVPacket pkt; av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; AVFrame *frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate frame\n"); exit(1); } int frame_count = 0; while (av_read_frame(fmt_ctx, &pkt) >= 0) { if (pkt.stream_index == video_stream_index) { ret = avcodec_send_packet(codec_ctx, &pkt); if (ret < 0) { fprintf(stderr, "Error sending packet to decoder\n"); exit(1); } while (ret >= 0) { ret = avcodec_receive_frame(codec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { fprintf(stderr, "Error during decoding\n"); exit(1); } char filename[256]; snprintf(filename, sizeof(filename), "frame-%d.yuv", frame_count); FILE *fp = fopen(filename, "wb"); if (!fp) { fprintf(stderr, "Could not open output file '%s'\n", filename); exit(1); } for (int i = 0; i < codec_ctx->height; i++) { fwrite(frame->data[0] + i * frame->linesize[0], 1, codec_ctx->width, fp); } for (int i = 0; i < codec_ctx->height / 2; i++) { fwrite(frame->data[1] + i * frame->linesize[1], 1, codec_ctx->width / 2, fp); } for (int i = 0; i < codec_ctx->height / 2; i++) { fwrite(frame->data[2] + i * frame->linesize[2], 1, codec_ctx->width / 2, fp); } fclose(fp); frame_count++; } } av_packet_unref(&pkt); } avcodec_free_context(&codec_ctx); avformat_close_input(&fmt_ctx); avformat_free_context(fmt_ctx); av_frame_free(&frame); return 0; } ``` 该示例代码从命令行读取一个输入文件名,并使用FFmpeg打开该文件。然后它搜索视频流并使用视频解码解码视频帧。对于每个解码帧,它将像素数据写入一个yuv文件。每个yuv文件的名称都是“frame-N.yuv”,其中N是帧计数器。最后,它释放所有使用的资源并退出。 请注意,此示例代码假定输入文件是可解码视频文件。如果您想处理其他类型的输入文件,请根据需要进行修改。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值