封了一个实时流录制接口,不做转码,直接收取实时流的数据包进行转存,可指定时长分段录制。实际应用接口需要并行录制多路视频流,并且可以通过启停信号来启动和停止录制。本篇不涉及多路实时流以及启停功能,只分享录制单路流的核心部分。
头文件:
#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;
}
功能测试正常。