一份基于FFMPEG的实时流录制接口

本文介绍了使用FFmpeg库进行实时流录制的核心代码实现,包括打开输入流、初始化输出、写入流到输出文件以及关闭文件等关键步骤。通过指定时长分段录制,不涉及多路流处理和启停功能。详细展示了如何处理单路视频流,对输入和输出格式上下文、流映射及时间戳转换进行了操作。
摘要由CSDN通过智能技术生成

封了一个实时流录制接口,不做转码,直接收取实时流的数据包进行转存,可指定时长分段录制。实际应用接口需要并行录制多路视频流,并且可以通过启停信号来启动和停止录制。本篇不涉及多路实时流以及启停功能,只分享录制单路流的核心部分。

 头文件:

#ifndef __VIDEO_REMUXER_H__
#define __VIDEO_REMUXER_H__

#include <stdio.h>
#include <string.h>
#include <iostream>

#ifdef __cplusplus
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libswscale/swscale.h"
#include "libavutil/opt.h"

}
#endif


class VideoRemuxer
{
public:
	VideoRemuxer();
	~VideoRemuxer();
	
	void input_init();
	void output_init();
	
	int open_input_file(const char* input_url);
	int open_output_file(const char* out_file_name);
	
	int write_stream_to_output_file(unsigned int time_len);
	
	void close_output_file();
	void close_input_file();
	
	
private:
	AVFormatContext *ifmt_ctx;    // input format context
	AVFormatContext *ofmt_ctx;    // output format context
	AVOutputFormat *ofmt;   //output format   
	AVPacket pkt;
	
	int *stream_mapping;
	int stream_mapping_size;

	
};


#endif

具体实现:

#include "video_remuxer.h"

using namespace std;

void VideoRemuxer::input_init()
{
	ifmt_ctx = NULL;
	stream_mapping = NULL;	
	stream_mapping_size = 0;

	av_register_all();
}

void VideoRemuxer::output_init()
{
	ofmt_ctx = NULL;
	ofmt = NULL;	
}


VideoRemuxer::VideoRemuxer()
{
	input_init();
}

VideoRemuxer::~VideoRemuxer()
{

}

int VideoRemuxer::open_input_file(const char* input_url)
{
	int ret;

	/* Open input url and get its format */
	ret = avformat_open_input(&ifmt_ctx, input_url, 0, 0);
	if(ret < 0)
	{
		cout << "Failed to open input url: " << input_url << endl;
		return ret;
	}

	/* Find input stream information */
	ret = avformat_find_stream_info(ifmt_ctx, 0);
	if(ret < 0)
	{
		cout << "Failed to find input stream info!" << endl;
		return ret;
	}

	/* Output input format information */
	av_dump_format(ifmt_ctx, 0, input_url, 0);

	/* Do stream mapping */
	stream_mapping_size = ifmt_ctx->nb_streams;
	stream_mapping = (int*)av_malloc_array(stream_mapping_size, sizeof(*stream_mapping));
	if(!stream_mapping)
	{
		cout << "Allocate stream_mapping failed!" << endl; 
		ret = AVERROR(ENOMEM);
		return ret;
	}

	return 0;
}


int VideoRemuxer::open_output_file(const char* out_file_name)
{
	int ret = 0;
	int stream_index = 0;
	
	/* Allocate AVFormatContext for output */
	avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_file_name);
	if(!ofmt_ctx)
	{
		cout << "Failed to create output context!" << endl;
		
	}

	ofmt = ofmt_ctx->oformat;

	/* Map the input stream index to output file stream index */
	for(int i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		AVStream *out_stream = NULL;
		AVStream *in_stream = ifmt_ctx->streams[i];
		AVCodecParameters *in_codecpar = in_stream->codecpar;

		/* Only record audio, video and subtitle data, and ignore other data */
		if((in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO) &&
			(in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO) &&
			(in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE))
		{
			stream_mapping[i] = -1;
			continue;
		}

		stream_mapping[i] = stream_index++;

		/* Allocate a new stream for output file */
		out_stream = avformat_new_stream(ofmt_ctx, NULL);
		if(!out_stream)
		{
			cout << "Failed to allocate output stream!" << endl;
			return -1;
		}

		/* Copy input stream codec parameters to output stream */
		ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
		if(ret < 0)
		{
			cout << "Failed to copy codec parameters!" << endl;
			return ret;
		}

		out_stream->codecpar->codec_tag = 0;
	}

	av_opt_set_defaults(ofmt_ctx->priv_data);
	av_opt_set_int(ofmt_ctx->priv_data, "hls_list_size", 0, 0);
	av_opt_set_int(ofmt_ctx->priv_data, "hls_time", 10, 0);

	/* Print output file information */
	av_dump_format(ofmt_ctx, 0, out_file_name, 1);

	/* Open output file and create AVIOContext */
	if(!(ofmt->flags & AVFMT_NOFILE))
	{
		ret = avio_open(&ofmt_ctx->pb, out_file_name, AVIO_FLAG_WRITE);
		if(ret < 0)
		{
			cout << "Failed to open output file!" << endl;
			return ret;
		}
	}

	cout << "Open output file OK!" << endl;
	
	ofmt_ctx->oformat->flags = ofmt_ctx->oformat->flags | AVFMT_TS_NEGATIVE | AVFMT_TS_NONSTRICT | AVFMT_TS_DISCONT;

	/* Write output file header */
	ret = avformat_write_header(ofmt_ctx, NULL);
	if(ret < 0)
	{
		cout << "Failed to write output file header!" << endl;
		return ret;
	}

	cout << "Write output file header OK!" << endl;

	return 0;
	
}


int VideoRemuxer::write_stream_to_output_file(unsigned int time_len)
{
	AVPacket pkt;
	int pkt_num = 0;
	bool b_started = false;
	bool b_stop = false;
	int64_t first_dts = 0;
	int64_t video_duration_ts = 0;  // Recorded video time duaraion, using timestamp 
	int64_t video_duration_accum = 0;   // Accumulation of packet duration
	
	
	while(1)
	{
		AVStream *in_stream = NULL;
		AVStream *out_stream = NULL;
		int ret = 0;

		ret = av_read_frame(ifmt_ctx, &pkt);
		if(ret < 0)
		{
			cout << "Read packet from input file failed!" << endl;
			break;
		}

		//cout << "stream_mapping_size = " << stream_mapping_size << endl;
		//cout << "pkt.stream_index = " << pkt.stream_index << ", stream_mapping[pkt.stream_index] = " << stream_mapping[pkt.stream_index] << endl;
		
		in_stream = ifmt_ctx->streams[pkt.stream_index];
		if((pkt.stream_index >= stream_mapping_size) || (stream_mapping[pkt.stream_index] < 0))
		{
			cout << "Invalid stream index!" << endl;
			
			av_packet_unref(&pkt);
			continue;
		}

		pkt.stream_index = stream_mapping[pkt.stream_index];
		out_stream = ofmt_ctx->streams[pkt.stream_index];

		//pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
		//pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
		//pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		if(!b_started && (pkt.flags & AV_PKT_FLAG_KEY))
		{
			cout << "Find a key frame packet, will start recording." << endl;
			b_started = true;
			first_dts = pkt.dts;
		}
		else if(!b_started)
		{
			av_packet_unref(&pkt);
			continue;
		}

		/* timestamp is shifted to from zero */
		pkt.dts -= first_dts;
		pkt.pts -= first_dts;

		/* Accumulate all packets duration */
		video_duration_accum += pkt.duration;

		/* When recorded file duration reached the required time length, stop current file recording */
		if(pkt.flags & AV_PKT_FLAG_KEY)
		{
			video_duration_ts = av_rescale_q(pkt.dts, out_stream->time_base, AV_TIME_BASE_Q);
			int64_t video_duration_accum_ms = av_rescale_q(video_duration_accum, in_stream->time_base, AV_TIME_BASE_Q);
			
			if((video_duration_ts > (time_len * AV_TIME_BASE)) || (video_duration_accum_ms > time_len * AV_TIME_BASE))
			{
				cout << "video_duration_ts = " << video_duration_ts << ", video_duration_accum = " << video_duration_accum_ms << endl;
				cout << "Output file duration reached required value, will stop current file." << endl;
				b_stop = true;
			}
		}
		
		/* Timestamp conversion from input stream to output stream */
		pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);
		pkt.dts = av_rescale_q(pkt.dts, in_stream->time_base, out_stream->time_base);
		pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		out_stream->cur_dts = pkt.dts;

		pkt.pos = -1;

		/* Write packet to output file */
		ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
		//ret = av_write_frame(ofmt_ctx, &pkt);
		if(ret < 0)
		{
			cout << "Error muxing packet!" << endl;
			return ret;
		}

		/* Release packet space */
		av_packet_unref(&pkt);

		if(b_stop)
		{
			break;
		}
	}

	/* Write output file trailer, and release some private data */
	av_write_trailer(ofmt_ctx);

	return 0;
}


void VideoRemuxer::close_output_file()
{
	if(ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
	{
		avio_closep(&ofmt_ctx->pb);
	}
	
	if(ofmt_ctx)
	{
		avformat_free_context(ofmt_ctx);
	}
	cout << "free ofmt_ctx OK!" << endl;
}

void VideoRemuxer::close_input_file()
{
	//操作完成后,一定要调用该函数来关闭输入文件并释放AVFormatContext 
	if(ifmt_ctx)
	{
		avformat_close_input(&ifmt_ctx);		
	}

	cout << "Close ifmt_ctx OK!" << endl;

	av_freep(&stream_mapping);

}

测试demo:

#include "video_remuxer.h"

using namespace std;

int main()
{
	int ret = 0;
	const char* input_url =  "rtsp://admin:123456@192.168.20.199/h264/ch1/main/av_stream";
	const char* output_file_name = "out.m3u8";

	VideoRemuxer video_remuxer;
	int file_num = 0;
	const int file_num_total = 3;
	
	video_remuxer.open_input_file(input_url);

	while(1)
	{
		video_remuxer.output_init();
		
		ret = video_remuxer.open_output_file(output_file_name);
		if(ret != 0)
        {
            cout << "Open output file failed!" << endl;
            break;
        }

		video_remuxer.write_stream_to_output_file(60);  /* input is seconds */

		video_remuxer.close_output_file();

		/* Test for recording specified number of files */
		if(file_num++ > file_num_total)
		{
			break;
		}
		
	}
		
	video_remuxer.close_input_file();
	
	return 0;	 

}

功能测试正常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值