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
:表示没有输出了,解码结束了。
返回其他负数表示失败。
我们这里封装了一个叫VideoDecoder
的类,方便调用
头文件VideoDecoder.h
#ifndef TMEDIADEMO_VIDEODECODER_H
#define TMEDIADEMO_VIDEODECODER_H
#include
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或者留言。