您的当前位置:首页正文

使用FFmpeg解码H264

2024-11-24 来源:个人技术集锦

接口定义

使用FFmpeg解码之前,先定义一些接口:

  • 打开解码器:Open
  • 关闭解码器:Close
  • 解码:Decode

根据上面的定义,可以使用C++来抽象出一个虚基类

namespace toy {
	class VideoDecoder {
	public:
		struct Setting {
			// TODO
		};

		virtual ~VideoDecoder() {};

		// 打开解码器
		virtual bool Open(const Setting& setting) = 0;

		// 解码
		virtual bool Decode(
			const uint8_t* pkt,
			const size_t pkt_size,
			std::shared_ptr<uint8_t>& frame,
			size_t& frame_size,
			uint32_t& width,
			uint32_t& height) = 0;

		// 关闭解码器
		virtual void Close() = 0;
	};
}

使用FFmpeg解码H264

头文件定义:

#pragma once
#include <stdio.h>
#include <memory>
#include <stdint.h>
#define __STDC_CONSTANT_MACROS
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>
#ifdef __cplusplus
};
#endif

#include "video_codec.h"

class FFmpegVideoDecoder:public toy::VideoDecoder
{
public:
	FFmpegVideoDecoder();

	virtual ~FFmpegVideoDecoder();

	virtual bool Open(const Setting& setting);

	virtual bool Decode(
		const uint8_t* pkt,
		const size_t pkt_size,
		std::shared_ptr<uint8_t>& frame,
		size_t& frame_size,
		uint32_t& width, 
		uint32_t& height);

	virtual void Close();
private:
	AVCodecContext* video_codec_ctx_;
	AVFrame* raw_frame_;
	Setting decoder_setting_;
};

源文件实现:

#include "ffmpeg_video_decoder.h"

FFmpegVideoDecoder::FFmpegVideoDecoder()
{
	av_register_all();
	avcodec_register_all();
	avformat_network_init();

	video_codec_ctx_ = 0;
	raw_frame_ = 0;
}

FFmpegVideoDecoder::~FFmpegVideoDecoder() {
	Close();
}

void FFmpegVideoDecoder::Close() {
	if (raw_frame_) {
		av_frame_free(&raw_frame_);
		raw_frame_ = NULL;
	}

	if (video_codec_ctx_) {
		avcodec_close(video_codec_ctx_);
		avcodec_free_context(&video_codec_ctx_);
		video_codec_ctx_ = NULL;
	}
}

bool FFmpegVideoDecoder::Open(const Setting& setting) {

	decoder_setting_ = setting;

	raw_frame_ = av_frame_alloc();
	
	// 查找H264解码器
	AVCodecID codec_id = AV_CODEC_ID_H264;

	AVCodec* codec = avcodec_find_decoder(codec_id);
	if (codec == NULL) {
		fprintf(stderr, "Codec not found.\n");
		Close();
		return false;
	}
	video_codec_ctx_ = avcodec_alloc_context3(codec);
	if (!video_codec_ctx_) {
		printf("Could not allocate video codec context\n");
		Close();
		return -1;
	}
	if (avcodec_open2(video_codec_ctx_, codec, NULL) < 0) {
		fprintf(stderr, "Could not open codec.\n");
		Close();
		return false;
	}

	return true;
}

// 创建智能数组
#define make_array(size) std::shared_ptr<uint8_t>(new uint8_t[size], std::default_delete<uint8_t[]>())

// 解码
bool FFmpegVideoDecoder::Decode(
	const uint8_t* pkt, 
	const size_t pkt_size,
	std::shared_ptr<uint8_t>& frame,
	size_t& frame_size,
	uint32_t& width, 
	uint32_t& height) {

	frame = nullptr;
	frame_size = 0;
	width = 0;
	height = 0;

	int got_picture = 0;
	AVPacket video_packet;
	av_init_packet(&video_packet); //AV_INPUT_BUFFER_PADDING_SIZE

	video_packet.data = (uint8_t*)pkt;
	video_packet.size = pkt_size;

	int ret = avcodec_decode_video2(video_codec_ctx_,
		raw_frame_,
		&got_picture,
		&video_packet);

	av_packet_unref(&video_packet);

	if (ret <= 0) {
		char buf[1024] = { 0 };
		av_make_error_string(buf, 1024, ret);
		av_frame_unref(raw_frame_);
		fprintf(stderr, "Decode Error.%s\n", buf);
		return false;
	}

	if (got_picture) {
		int y_size = raw_frame_->width * raw_frame_->height;
		frame = make_array(y_size * 3 / 2);
		memmove(frame.get(), raw_frame_->data[0], y_size);
		memmove(frame.get() + y_size, raw_frame_->data[1], y_size / 4);
		memmove(frame.get() + y_size * 5 / 4, raw_frame_->data[2], y_size / 4);
		frame_size = y_size * 3 / 2;
		width = raw_frame_->width;
		height = raw_frame_->height;

	}
	av_frame_unref(raw_frame_);

	return true;
}

显示全文