ffmpeg 入门(三、解码视频帧)
·
三、解码视频帧
av_read_frame(fmt_ctx, packet)
这个函数的作用是读取一个视频包到 AVPacket 中
对于视频,数据包恰好包含一帧;对于音频,若每帧大小固定(如PCM或ADPCM),则包含整数个帧;若音频帧大小可变(如MPEG音频),则包含一帧。
这里有两段代码,区别在于是否循环调 avcodec_receive_frame。
上面的说明是 ffmpeg 对 av_read_frame 的注释,从这里看来,视频帧应该是不需要循环解码的,但是查找资料,都是需要循环解码的,考虑到 I 帧,P 帧,B帧之类的,即当前帧需依赖后续的帧来进行解码,所以可能后续输出多个帧。笔者尝试了几个视频,两个方法解码出的帧数并无差别,但查阅 ffmpeg ffmpeg/doc/examples/decode_video.c 后,里面也是循环接收帧的,因此还是建议用第二段代码。
while (av_read_frame(fmt_ctx, packet) >= 0) {
if (packet->stream_index == video_index) {
ret = avcodec_send_packet(video_decode_ctx, packet);
if (ret == 0) {
ret = avcodec_receive_frame(video_decode_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
continue;
} else if (ret < 0) {
return -1;
}
} else {
return -1;
}
}
av_packet_unref(packet); // 清除数据包
}
while (av_read_frame(fmt_ctx, packet) >= 0) {
if (packet->stream_index == video_index) {
ret = avcodec_send_packet(video_decode_ctx, packet);
while ( ret >= 0) {
ret = avcodec_receive_frame(video_decode_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
return -1;
}
}
}
av_packet_unref(packet);
}
ffmpeg 官方 demo
/*
* Copyright (c) 2001 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @file libavcodec video decoding API usage example
* @example decode_video.c *
*
* Read from an MPEG1 video file, decode frames, and generate PGM images as
* output.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#define INBUF_SIZE 4096
static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
char *filename)
{
FILE *f;
int i;
f = fopen(filename,"wb");
fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
for (i = 0; i < ysize; i++)
fwrite(buf + i * wrap, 1, xsize, f);
fclose(f);
}
static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
const char *filename)
{
char buf[1024];
int ret;
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0) {
fprintf(stderr, "Error sending a packet for decoding\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) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
printf("saving frame %3"PRId64"\n", dec_ctx->frame_num);
fflush(stdout);
/* the picture is allocated by the decoder. no need to
free it */
snprintf(buf, sizeof(buf), "%s-%"PRId64, filename, dec_ctx->frame_num);
pgm_save(frame->data[0], frame->linesize[0],
frame->width, frame->height, buf);
}
}
int main(int argc, char **argv)
{
const char *filename, *outfilename;
const AVCodec *codec;
AVCodecParserContext *parser;
AVCodecContext *c= NULL;
FILE *f;
AVFrame *frame;
uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data;
size_t data_size;
int ret;
int eof;
AVPacket *pkt;
if (argc <= 2) {
fprintf(stderr, "Usage: %s <input file> <output file>\n"
"And check your input file is encoded by mpeg1video please.\n", argv[0]);
exit(0);
}
filename = argv[1];
outfilename = argv[2];
pkt = av_packet_alloc();
if (!pkt)
exit(1);
/* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
/* find the MPEG-1 video decoder */
codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO);
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
parser = av_parser_init(codec->id);
if (!parser) {
fprintf(stderr, "parser not found\n");
exit(1);
}
c = avcodec_alloc_context3(codec);
if (!c) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
/* For some codecs, such as msmpeg4 and mpeg4, width and height
MUST be initialized there because this information is not
available in the bitstream. */
/* open it */
if (avcodec_open2(c, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
f = fopen(filename, "rb");
if (!f) {
fprintf(stderr, "Could not open %s\n", filename);
exit(1);
}
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate video frame\n");
exit(1);
}
do {
/* read raw data from the input file */
data_size = fread(inbuf, 1, INBUF_SIZE, f);
if (ferror(f))
break;
eof = !data_size;
/* use the parser to split the data into frames */
data = inbuf;
while (data_size > 0 || eof) {
ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0) {
fprintf(stderr, "Error while parsing\n");
exit(1);
}
data += ret;
data_size -= ret;
if (pkt->size)
decode(c, frame, pkt, outfilename);
else if (eof)
break;
}
} while (!eof);
/* flush the decoder */
decode(c, frame, NULL, outfilename);
fclose(f);
av_parser_close(parser);
avcodec_free_context(&c);
av_frame_free(&frame);
av_packet_free(&pkt);
return 0;
}
视频解码完整示例,其中 save_frame_to_image 用于把视频帧保存成图片,以便查看解码效果,在后续文章中将详细介绍,目前可忽略。
#include <string>
extern "C" { // 用C规则编译指定的代码
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libswscale/swscale.h"
}
using namespace std;
// 保存成图片,可以先忽略
bool save_frame_to_image(AVFrame* frame, const string& filename) {
if (!frame) {
return false;
}
// 格式转换
AVFrame* rgb_frame = av_frame_alloc();
if (!rgb_frame) {
return false;
}
rgb_frame->format = AV_PIX_FMT_RGB24;
rgb_frame->width = frame->width;
rgb_frame->height = frame->height;
int ret = av_frame_get_buffer(rgb_frame, 0);
if (ret < 0) {
av_frame_free(&rgb_frame);
return false;
}
SwsContext* sws_ctx = sws_getContext(
frame->width, frame->height, (AVPixelFormat)frame->format,
rgb_frame->width, rgb_frame->height, AV_PIX_FMT_RGB24,
SWS_BILINEAR, nullptr, nullptr, nullptr
);
if (!sws_ctx) {
av_frame_free(&rgb_frame);
return false;
}
ret = sws_scale(
sws_ctx,
frame->data, frame->linesize,
0, frame->height,
rgb_frame->data, rgb_frame->linesize
);
// 查找编码器
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_PNG);
if (!codec) {
return false;
}
// 分配编码器上下文
AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
return false;
}
// 设置编码器参数
codec_ctx->width = frame->width;
codec_ctx->height = frame->height;
codec_ctx->pix_fmt = AV_PIX_FMT_RGB24;
codec_ctx->time_base = {1, 25};
// 打开编码器
ret = avcodec_open2(codec_ctx, codec, nullptr);
if (ret < 0) {
avcodec_free_context(&codec_ctx);
return false;
}
// 分配输出格式上下文
AVFormatContext* fmt_ctx = nullptr;
ret = avformat_alloc_output_context2(&fmt_ctx, nullptr, nullptr, filename.c_str());
if (ret < 0) {
avcodec_free_context(&codec_ctx);
return false;
}
// 添加输出流
AVStream* stream = avformat_new_stream(fmt_ctx, nullptr);
if (!stream) {
avcodec_free_context(&codec_ctx);
avformat_free_context(fmt_ctx);
return false;
}
stream->time_base = codec_ctx->time_base;
avcodec_parameters_from_context(stream->codecpar, codec_ctx);
// 打开输出文件
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&fmt_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE);
if (ret < 0) {
avcodec_free_context(&codec_ctx);
avformat_free_context(fmt_ctx);
return false;
}
}
// 写入文件头
ret = avformat_write_header(fmt_ctx, nullptr);
if (ret < 0) {
avcodec_free_context(&codec_ctx);
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&fmt_ctx->pb);
}
avformat_free_context(fmt_ctx);
return false;
}
AVPacket* packet = av_packet_alloc();
if (!packet) {
avcodec_free_context(&codec_ctx);
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&fmt_ctx->pb);
}
avformat_free_context(fmt_ctx);
return false;
}
rgb_frame->pts = 0;
// 发送 frame 到编码器
ret = avcodec_send_frame(codec_ctx, rgb_frame);
if (ret < 0) {
av_packet_free(&packet);
avcodec_free_context(&codec_ctx);
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&fmt_ctx->pb);
}
avformat_free_context(fmt_ctx);
return false;
}
// 从编码器接收 packet 并写入文件
while (ret >= 0) {
ret = avcodec_receive_packet(codec_ctx, packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
break;
}
av_packet_rescale_ts(packet, codec_ctx->time_base, stream->time_base);
packet->stream_index = stream->index;
// 写入 packet
ret = av_interleaved_write_frame(fmt_ctx, packet);
if (ret < 0) {
// break;
}
av_packet_unref(packet);
}
// 写入文件尾
av_write_trailer(fmt_ctx);
// 释放资源
sws_freeContext(sws_ctx);
av_packet_free(&packet);
avcodec_free_context(&codec_ctx);
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&fmt_ctx->pb);
}
avformat_free_context(fmt_ctx);
return true;
}
int main() {
AVFormatContext *fmt_ctx = NULL;
const char *filename = "/test.mp4";
// 已不再需要
// avformat_network_init();
// 打开输入文件
int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
if (ret < 0) {
return ret;
}
// 查找流信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
// 没有找到视频流信息
return ret;
}
// 查找视频流
int video_index = -1;
video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (video_index == -1) {
return -1;
}
AVStream *src_video = fmt_ctx->streams[video_index];
// 查找视频解码器
enum AVCodecID video_codec_id = src_video->codecpar->codec_id;
const AVCodec *video_codec = avcodec_find_decoder(video_codec_id);
if (!video_codec) {
return -1;
}
/*
* AVCodec 存储解码器的属性
* AVCodecContext 用于解码,编码操作
*/
// 分配解码器的实例
AVCodecContext *video_decode_ctx = avcodec_alloc_context3(video_codec);
// 把视频流中的编解码参数复制给解码器的实例
avcodec_parameters_to_context(video_decode_ctx, src_video->codecpar);
// 打开解码器的实例
ret = avcodec_open2(video_decode_ctx, NULL, NULL);
if (ret < 0) {
return ret;
}
/*
* 函数名带 alloc 基本就是用于创建对象 理解成 new 即可
*/
AVPacket* packet = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
av_packet_unref(packet);
int i = 0;
/*
* 对于视频,数据包恰好包含一帧;对于音频,若每帧大小固定(如PCM或ADPCM),
* 则包含整数个帧;若音频帧大小可变(如MPEG音频),则包含一帧。
*/
// 读取数据 这里是把视频包读到 packet , 函数名称则是历史遗留问题
while (av_read_frame(fmt_ctx, packet) >= 0) { // 轮询数据包
if (packet->stream_index == video_index) {
// 把未解压的数据包发给解码器实例
ret = avcodec_send_packet(video_decode_ctx, packet);
// 从解码器实例获取还原后的数据帧
while ( ret >= 0) {
av_frame_unref(frame);
ret = avcodec_receive_frame(video_decode_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
return -1;
}
i++;
string filepath = "/images/" + to_string(i) + ".png";
save_frame_to_image(frame, filepath);
}
}
av_packet_unref(packet);
}
// 关闭文件并释放上下文
avformat_close_input(&fmt_ctx);
return 0;
}
更多推荐



所有评论(0)