1. ffmpeg 视频解码一
2. ffmpeg 视频解码二
3. ffmpeg 音频解码一
4. ffmpeg 音频解码二
5. ffmpeg 音视频解码
6. ffmpeg 视频编码一
7. ffmpeg 视频编码一(精简版)
8. ffmpeg 视频编码二(基于 libswscale 转换视频)
9. ffmpeg 过滤器libavfilter的使用
10. ffmpeg 视频编码三(基于 libavfilter 转换视频)
前言
这是ffmpeg的第二篇,这篇主要实现一下使用ffmpeg的API实现解码,不使用和上篇的解析器做解析。
流程图
代码流程即如流程图所示,下面讲解一下当中部分函数的作用)。
- avformat_open_input
打开输入文件,并读取文件头相关信息 - avformat_find_stream_info
读取媒体文件信息。 - av_find_best_stream
获取视频流序号(因为文件当中可能既有音频也有视频,字幕等流,我们这里使用这个函数获取视频流的序号)。 - avcodec_find_decoder
获取解码器 - avcodec_parameters_to_context
我们自己构建的解码器并没有设置一些解码相关的参数,此时我们拷贝视频流的参数到里面即可。 - av_read_frame
从视频当中读取数据(一帧),不用和上篇一样,还需要我们使用解析器解析成一帧。 - avcodec_send_packet
发送我们刚刚得到的解析数据到解码器做解码。 - avcodec_receive_frame
获取解码之后的数据。
源码
#pragma once
#define __STDC_CONSTANT_MACROS
#define _CRT_SECURE_NO_WARNINGS
extern "C"
{
#include <libavformat/avformat.h>
#include "libavcodec/avcodec.h"
}
#define INBUF_SIZE 4096
using namespace std;
#define INPUT_FILE_NAME "lh_online.h264"
#define OUTPUT_FILE_NAME "lh_online.yuv"
static void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt,
FILE* ofile)
{
int ret;
int y_size;
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "发送数据包到解码器出错。\n");
exit(1);
}
while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error sending a packet for decoding.\n");
exit(1);
}
//此时一帧视频已经保存到frame中了
//
//打印输出的视频帧的帧数
av_log(NULL, AV_LOG_INFO, "saving frame:%d , width: %d ,height: %d.\n", dec_ctx->frame_number, frame->width, frame->height);
//获取一帧视频数据大小
y_size = frame->width * frame->height;
fwrite(frame->data[0], 1, y_size, ofile); //Y
fwrite(frame->data[1], 1, y_size / 4, ofile); //U
fwrite(frame->data[2], 1, y_size / 4, ofile); //V
}
}
int main(int argc, char* argv[])
{
const AVCodec* codec;
AVFormatContext* fmt_ctx = NULL;
AVCodecContext* c = NULL;
AVStream* st;
AVFrame* frame;
AVPacket* pkt;
int ret;
int stream_index;
FILE* ofile;
//打开输入文件,并为fmt_ctx分配空间
if (avformat_open_input(&fmt_ctx, INPUT_FILE_NAME, NULL, NULL)) {
av_log(NULL, AV_LOG_ERROR, "Codec not open source file.\n");
exit(1);
}
//获取流信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not find stream information.\n");
exit(1);
}
//获取视频流序号(这里我们明确要解码的是视频,也只处理视频)
stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (stream_index < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot find stream index\n");
exit(1);
}
//获取文件流
st = fmt_ctx->streams[stream_index];
//获取解码器(这里不需要我们显示的指定了)
codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec) {
av_log(NULL, AV_LOG_ERROR, "Codec not found.\n");
exit(1);
}
//分配解析器上下文
c = avcodec_alloc_context3(codec);
if (!c) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.\n");
exit(1);
}
//把输入流的编解码参数复制到我们的解码器上
if (avcodec_parameters_to_context(c, st->codecpar) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to copy %s codec parameters to decoder context\n");
exit(1);
}
//打开解码器
if (avcodec_open2(c, codec, NULL) < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not open codec.\n");
exit(1);
}
//分配AVPacket
pkt = av_packet_alloc();
if (!pkt) {
exit(1);
}
//分配AVFrame
frame = av_frame_alloc();
if (!frame) {
exit(1);
}
//打开输出文件
ofile = fopen(OUTPUT_FILE_NAME, "wb+");
if (!ofile) {
av_log(NULL, AV_LOG_ERROR, "Could not open \s.\n", OUTPUT_FILE_NAME);
exit(1);
}
//从文件读取帧
while (av_read_frame(fmt_ctx, pkt) >= 0) {
//只处理视频流
if (pkt->stream_index==stream_index) {
decode(c, frame, pkt, ofile);
}
av_packet_unref(pkt);
}
//flush 解码器
decode(c, frame, NULL, ofile);
//资源释放
fclose(ofile);
avcodec_free_context(&c);
av_frame_free(&frame);
av_packet_free(&pkt);
return 0;
}
这个示例中和上篇文章一样,还是把一个H264编码的 lh_online.h264 的文件解码成原始YUV视频文件 lh_online.yuv。
播放还是使用ffplay来实现,下面是播放的代码。
ffplay -i D:\project\ffmpeg_demo\decode_video_by_api\lh_online.yuv -pix_fmt yuv420p -s 512*288
注意两点:
- 此时我们播放的数据格式是 yuv 的(此处可设置可不设置,ffplay默认可播放)。
- 我们需要知道视频的原始宽高,代码里有个位置获取了宽高的(dec_ctx->frame_number, frame->width, frame->height),我们需要把快其设置到命令里,否者是不能播放的(我这个文件是512*288的)。
下面看下效果。
到此,视频解码相关的API即介绍完了。
相对于上篇文章基于parser的方式来的话,这篇文章的相对来说代码逻辑是比较直观的,也不需要我们自己去考虑分割视频帧相关的问题(平常我们使用最多的应该也是这种方式)。
需要注意的就是我们需要 avformat.h这个文件头,这个文件封装的里面即是媒体文件格式相关的API。