FFmpeg拉流器

音视频应用开发系列文章目录

拉流器设计思路

生产者-消费者模式。一个线程负责拉取原始音视频流然后存储在队列,音频消费线程从音频队列获取原始流进行解码等其他消费,视频消费线程从视频队列获取原始流进行解码等其他消费。

设计AvPuller类

AvPuller.h

#ifndef _AV_PULLER_H_
#define _AV_PULLER_H_

/***********************************************************
**	Author:kaychan
**	Data:2019-11-04
**	Mail:1203375695@qq.com
**	Explain:a audio/video puller
***********************************************************/

#include "AvCommon.h"

// 获取到解码帧后的回调函数
typedef void(*on_frame)(AVStream *stream, AVFrame *frame, void *priv);
// 错误回调函数
typedef void(*on_error)(std::string error, void *priv);

typedef struct AvPullerParamters_S {

public:
	void *priv = NULL;
	std::string stream = "";
	enum AVHWDeviceType hw_dev_type = AV_HWDEVICE_TYPE_NONE;
	enum AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;
	AVBufferRef *hw_dev_ctx = NULL;
	bool run = true;
	AVFormatContext *fmt_ctx = NULL;
	AVCodecContext *audio_cc = NULL;
	AVCodecContext *video_cc = NULL;
	int64_t cache_packets_size = 0;
	AvPacketSafeQueue audio_packets;
	AvPacketSafeQueue video_packets;
	AVFrame *audio_frame = NULL;
	AVFrame *video_frame = NULL;
	AVFrame *video_hw2sw_frame = NULL;
	int audio_idx = -1;
	int video_idx = -1;
	on_frame audio_on_frame;
	on_frame video_on_frame;
	on_error av_on_error;
}AvPullerParamters;

class AvPuller {

public:
	AvPuller();
	// 启动拉流器
	// stream:流名
	// audio_on_frame:音频消费回调函数
	// video_on_frame:视频消费回调函数
	// on_error:错误信息回调函数
	// hw_dev_type:解码器类型支持硬件解码
	//				(可以参考本系列博文《FFmpeg视频解码硬件加速》使用)
	// priv:回调函数入参
	void start(const std::string stream,
		on_frame audio_on_frame, on_frame video_on_frame, on_error av_on_error, 
		enum AVHWDeviceType hw_dev_type = AV_HWDEVICE_TYPE_NONE, 
		void *priv = NULL);
	// 停止拉流器
	void stop();

private:
	std::thread tsk_p_av_;
	std::thread tsk_c_a_;
	std::thread tsk_c_v_;
	AvPullerParamters paramters_;
};

#endif

AvPuller.cpp

#include "AvPuller.h"

static std::string AnsiToUTF8(const char *_ansi, int _ansi_len) {

	std::string str_utf8("");
	wchar_t* pUnicode = NULL;
	BYTE * pUtfData = NULL;
	do {

		int unicodeNeed = MultiByteToWideChar(CP_ACP, 0, _ansi, _ansi_len, NULL, 0);
		pUnicode = new wchar_t[unicodeNeed + 1];
		memset(pUnicode, 0, (unicodeNeed + 1) * sizeof(wchar_t));
		int unicodeDone = MultiByteToWideChar(CP_ACP, 0, _ansi, _ansi_len, (LPWSTR)pUnicode, unicodeNeed);
		if (unicodeDone != unicodeNeed) break;
		int utfNeed = WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)pUnicode, unicodeDone, (char *)pUtfData, 0, NULL, NULL);
		pUtfData = new BYTE[utfNeed + 1];
		memset(pUtfData, 0, utfNeed + 1);
		int utfDone = WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)pUnicode, unicodeDone, (char *)pUtfData, utfNeed, NULL, NULL);
		if (utfNeed != utfDone) break;
		str_utf8.assign((char *)pUtfData);
	} while (false);
	if (pUnicode) delete[] pUnicode;
	if (pUtfData) delete[] pUtfData;
	return str_utf8;
}

enum AVPixelFormat global_hw_pix_fmt = AV_PIX_FMT_NONE;

static enum AVPixelFormat on_get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) {

	const enum AVPixelFormat *p;
	for (p = pix_fmts; *p != -1; p++) {
		if (*p == global_hw_pix_fmt)
			return *p;
	}
	return AV_PIX_FMT_NONE;
}

static inline void emit_error(int errcode, AvPullerParamters &paramters) {

	paramters.run = false;
	char error[256];
	av_strerror(errcode, error, 256);
	paramters.av_on_error(error, paramters.priv);
}

static inline void emit_error(std::string errstr, AvPullerParamters &paramters) {

	paramters.run = false;
	paramters.av_on_error(errstr, paramters.priv);
}

void AvPuller_p_av(AvPullerParamters &paramters) {

	// open media input
	AVInputFormat *ifmt = NULL;
	std::string::size_type sta = paramters.stream.find("audio="); 
	std::string::size_type stv = paramters.stream.find("video=");
	if (sta != std::string::npos || stv != std::string::npos) {

		ifmt = av_find_input_format(AVENGINE_MEDIA_DRIVER);
	}
	std::string url_utf8 = AnsiToUTF8(paramters.stream.c_str(), paramters.stream.length());
	AVDictionary *dict = NULL;
	av_dict_set(&dict, "stimeout", "2000000", 0);
	av_dict_set(&dict, "buffer_size", "4096000", 0);
	av_dict_set(&dict, "recv_buffer_size", "4096000", 0);
	int r = avformat_open_input(&paramters.fmt_ctx, url_utf8.c_str(), ifmt, &dict);
	if (r != 0) {

		emit_error(r, paramters);
		return;
	}
	r = avformat_find_stream_info(paramters.fmt_ctx, NULL);
	if (r < 0) {

		emit_error(r, paramters);
		return;
	}
	// find media stream index
	AVCodec *ac, *vc;
	paramters.audio_idx = av_find_best_stream(paramters.fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &ac, 0);
	paramters.video_idx = av_find_best_stream(paramters.fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &vc, 0);
	// open media stream decoder
	if (paramters.audio_idx >= 0) {

		paramters.audio_cc = avcodec_alloc_context3(ac);
		avcodec_parameters_to_context(paramters.audio_cc, paramters.fmt_ctx->streams[paramters.audio_idx]->codecpar);
		av_codec_set_pkt_timebase(paramters.audio_cc, paramters.fmt_ctx->streams[paramters.audio_idx]->time_base);
		r = avcodec_open2(paramters.audio_cc, ac, NULL);
		if (r == 0) {

			paramters.audio_frame = av_frame_alloc();
			if (!paramters.audio_frame) {

				emit_error("av_frame_alloc failed", paramters);
				return;
			}
		}
		else {

			emit_error(r, paramters);
			return;
		}
	}
	if (paramters.video_idx >= 0) {

		paramters.video_cc = avcodec_alloc_context3(vc);
		avcodec_parameters_to_context(paramters.video_cc, paramters.fmt_ctx->streams[paramters.video_idx]->codecpar);
		av_codec_set_pkt_timebase(paramters.video_cc, paramters.fmt_ctx->streams[paramters.video_idx]->time_base);
		// check video decode device
		if (paramters.hw_dev_type != AV_HWDEVICE_TYPE_NONE) {

			paramters.hw_pix_fmt = AV_PIX_FMT_NONE;
			int iii = 0;
			for (;;) {

				const AVCodecHWConfig *config = avcodec_get_hw_config(vc, iii++);
				if (config) {

					if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
						config->device_type == paramters.hw_dev_type) {

						paramters.hw_pix_fmt = config->pix_fmt;
						r = av_hwdevice_ctx_create(&paramters.hw_dev_ctx, paramters.hw_dev_type, NULL, NULL, 0);
						if (r == 0) {

							global_hw_pix_fmt = paramters.hw_pix_fmt;
							paramters.video_cc->get_format = on_get_hw_format;
							paramters.video_cc->hw_device_ctx = av_buffer_ref(paramters.hw_dev_ctx);
						}
						break; // support
					}
				}
				else break; // the hw not support the codec
			}
		}
		r = avcodec_open2(paramters.video_cc, vc, NULL);
		if (r == 0) {

			paramters.video_frame = av_frame_alloc();
			paramters.video_hw2sw_frame = av_frame_alloc();
			if (!paramters.video_frame || !paramters.video_hw2sw_frame) {

				emit_error("av_frame_alloc failed", paramters);
				return;
			}
		}
		else {

			emit_error(r, paramters);
			return;
		}
	}

	// start produce audio and video
	while (paramters.run) {
		
		AVPacket packet;
		paramters.cache_packets_size = paramters.audio_packets.size() + paramters.video_packets.size();
		if (paramters.cache_packets_size < 1 * MB) {

			r = av_read_frame(paramters.fmt_ctx, &packet);
			if (r >= 0) {

				if (packet.stream_index == paramters.audio_idx) paramters.audio_packets.eq(packet);
				if (packet.stream_index == paramters.video_idx) paramters.video_packets.eq(packet);
			}
			else {

				// maybe rtsp/rtmp has disconnected
				// maybe file is eof
				break;
			}
		}
		else CPP11_MSLEEP(30);
	}
}

void AvPuller_c_a(AvPullerParamters &paramters) {

	// start consume audio
	while (paramters.run) {

		if (!paramters.audio_packets.empty()) {

			AVPacket p = paramters.audio_packets.dq();
			if (!p.data) continue;
			int r = avcodec_send_packet(paramters.audio_cc, &p);
			av_packet_unref(&p);
			if (r == 0) {

				r = avcodec_receive_frame(paramters.audio_cc, paramters.audio_frame);
				if (r == 0) { 

					paramters.audio_on_frame(paramters.fmt_ctx->streams[paramters.audio_idx],
						paramters.audio_frame, paramters.priv);
				}
			}
		}else CPP11_MSLEEP(30);
	}
}

void AvPuller_c_v(AvPullerParamters &paramters) {

	// start consume video
	while (paramters.run) {

		if (!paramters.video_packets.empty()) {

			AVPacket p = paramters.video_packets.dq();
			if (!p.data) continue;
			int r = avcodec_send_packet(paramters.video_cc, &p);
			av_packet_unref(&p);
			if (r == 0) {

				r = avcodec_receive_frame(paramters.video_cc, paramters.video_frame);
				if (r == 0) {

					AVFrame *tf = paramters.video_frame;
					if (paramters.video_frame->format == paramters.hw_pix_fmt) {

						r = av_hwframe_transfer_data(paramters.video_hw2sw_frame, paramters.video_frame, 0);
						if (r == 0)
							tf = paramters.video_hw2sw_frame;
					}
					tf->best_effort_timestamp = paramters.video_frame->best_effort_timestamp;
					paramters.video_on_frame(paramters.fmt_ctx->streams[paramters.video_idx],
						tf, paramters.priv);
				}
			}
		}
		else CPP11_MSLEEP(30);
	}
}

AvPuller::AvPuller() {

}

void AvPuller::start(const std::string stream,
	on_frame audio_on_frame, on_frame video_on_frame, on_error av_on_error,
	enum AVHWDeviceType hw_dev_type,
	void *priv) {

	paramters_.stream = stream;
	paramters_.audio_on_frame = audio_on_frame;
	paramters_.video_on_frame = video_on_frame;
	paramters_.av_on_error = av_on_error;
	paramters_.hw_dev_type = hw_dev_type;
	paramters_.priv = priv;
	paramters_.run = true;
	tsk_p_av_ = std::thread(AvPuller_p_av, std::ref(paramters_));
	tsk_c_a_ = std::thread(AvPuller_c_a, std::ref(paramters_));
	tsk_c_v_ = std::thread(AvPuller_c_v, std::ref(paramters_));
}

void AvPuller::stop() {

	paramters_.run = false;
	if (tsk_p_av_.joinable()) tsk_p_av_.join();
	if (tsk_c_a_.joinable()) tsk_c_a_.join();
	if (tsk_c_v_.joinable()) tsk_c_v_.join();
	paramters_.audio_packets.clear();
	paramters_.video_packets.clear();
	if (paramters_.audio_frame) {

		av_frame_free(&paramters_.audio_frame);
		paramters_.audio_frame = NULL;
	}
	if (paramters_.video_frame) {

		av_frame_free(&paramters_.video_frame);
		paramters_.video_frame = NULL;
	}
	if (paramters_.video_hw2sw_frame) {

		av_frame_free(&paramters_.video_hw2sw_frame);
		paramters_.video_hw2sw_frame = NULL;
	}
	if (paramters_.audio_cc) {

		avcodec_close(paramters_.audio_cc);
		avcodec_free_context(&paramters_.audio_cc);
		paramters_.audio_cc = NULL;
	}
	if (paramters_.video_cc) {

		avcodec_close(paramters_.video_cc);
		avcodec_free_context(&paramters_.video_cc);
		paramters_.video_cc = NULL;
	}
	if (paramters_.hw_dev_ctx) {

		av_buffer_unref(&paramters_.hw_dev_ctx);
		paramters_.hw_dev_ctx = NULL;
	}
	if (paramters_.fmt_ctx) {

		avformat_close_input(&paramters_.fmt_ctx);
		paramters_.fmt_ctx = NULL;
	}
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值