输入H.264流,输出封装格式流

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013699869/article/details/50266335
//H264ToContainer_Win32.h
extern "C"
{
	//@param format_name 输出流的格式名
	//@param r_frame_rate 输入的H.264流帧率
	//@param buffer_size_max 输入数据的最大尺寸
	//@param callback 输出媒体数据的回调函数
	//@return 输出的数据结构指针,该数据结构包含了所有媒体信息
	__declspec(dllexport) void *format_initialise(const char *format_name, float r_frame_rate, int buffer_size_max,
		int(*callback)(void *opaque, uint8_t *buf, int buf_size));

	//@param fmt_ctx format_initialise()的返回值
	//@param data 每次输入的数据
	//@param buffer_size 每次输入的数据尺寸可以不同,但不能大于format_initialise()的参数buffer_size_max
	//@return 输出的数据结构指针,该数据结构包含了所有媒体信息
	__declspec(dllexport) void *format_data_import(void *fmt_ctx, uint8_t *data, int buffer_size);

	//@param fmt_ctx format_data_import()的返回值
	__declspec(dllexport) int format_close(void *fmt_ctx);
}

// H264ToContainer_Win32.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"

#define __STDC_CONSTANT_MACROS

extern "C"
{
#include "libavformat/avformat.h"
}
#include "H264ToContainer_Win32.h"

#define IO_BUFFER_SIZE 32768

void *format_initialise(const char *format_name, float r_frame_rate, int buffer_size_max, 
	                    int(*callback)(void *opaque, uint8_t *buf, int buf_size))
{
	AVOutputFormat *ofmt = NULL;
	AVFormatContext *ofmt_ctx = NULL;
	int ret;

	av_register_all();

	//用户传入的可能是扩展名,而扩展名和格式名可能不同,所以在此作转换。
	if (!strcmp(format_name, "mkv"))
		format_name = "matroska";
	else if (!strcmp(format_name, "ts"))
		format_name = "mpegts";
	else if (!strcmp(format_name, "mpg"))
		format_name = "mpeg";

	avformat_alloc_output_context2(&ofmt_ctx, NULL, format_name, NULL);
	if (!ofmt_ctx)
	{
		printf("Could not create output context\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}
	ofmt = ofmt_ctx->oformat;

	uint8_t *outbuffer = (uint8_t *)av_malloc(IO_BUFFER_SIZE);
	AVIOContext *avio_out = avio_alloc_context(outbuffer, IO_BUFFER_SIZE, 1, NULL, NULL, callback, NULL);
    ofmt_ctx->pb = avio_out;
	ofmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;

	AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
	if (!codec)
	{
		printf("Could not find encoder for '%s'\n",
			avcodec_get_name(AV_CODEC_ID_H264));
		goto end;
	}
	AVStream *out_stream = avformat_new_stream(ofmt_ctx, codec);
	if (!out_stream)
	{
		printf("Failed allocating output stream\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}
	out_stream->codec->codec_tag = 0;
	/* Some formats want stream headers to be separate. */
	if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
		out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;

	//虽然不起作用,但必须设置AVCodecContext中的width和height,
	//否则,调avformat_write_header()时,报错:dimensions not set.
	ofmt_ctx->streams[0]->codec->width = 1;
	ofmt_ctx->streams[0]->codec->height = 1;

	out_stream->r_frame_rate.num = r_frame_rate * 1000;
	out_stream->r_frame_rate.den = 1000;

	//Write file header
	if (avformat_write_header(ofmt_ctx, NULL) < 0)
	{
		printf("Error occurred when opening output file\n");
		goto end;
	}

	//buffer_size_max为每次传入数据的最大尺寸。
	//为ofmt_ctx->opaque分配空间要考虑到上一次可能剩余IDR帧尺寸最大值,预留448KB。
	//若分辨率极大,可能造成内存越界,增大分配尺寸即可。
	if (buffer_size_max < 65536) //64KB
	{
		//ofmt_ctx->opaque为临时缓冲区,存储上一次传入数据剩余的不完整帧和本次传入的数据。
		ofmt_ctx->opaque = (uint8_t *)av_malloc(524288); //512KB
	}
	else
	{
		//ofmt_ctx->opaque为临时缓冲区,存储上一次传入数据剩余的不完整帧和本次传入的数据。
		ofmt_ctx->opaque = (uint8_t *)av_malloc(458752 + buffer_size_max); //448KB+
	}

	return ofmt_ctx;

end:
	/* close output */
	if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
		avio_close(ofmt_ctx->pb);
	avformat_free_context(ofmt_ctx);
	return 0;
}

void *format_data_import(void *fmt_ctx, uint8_t *data, int buffer_size)
{
	AVFormatContext *ofmt_ctx = (AVFormatContext *)fmt_ctx;
	//上一次传入数据剩余的不完整帧的大小。
	static int residue_len = 0;
	static int frame_index = 0;
	memcpy((uint8_t *)ofmt_ctx->opaque + residue_len, data, buffer_size);
	int frame_header = 0;
	//之所以从j==1开始,是因为j==0必然是帧头,现在要找的是下一个帧头
	for (int j = 1; j <= residue_len + buffer_size - 5; j++)
	{
		if (((uint8_t *)ofmt_ctx->opaque)[j]     == 0x00 && 
			((uint8_t *)ofmt_ctx->opaque)[j + 1] == 0x00 && 
			((uint8_t *)ofmt_ctx->opaque)[j + 2] == 0x00 && 
			((uint8_t *)ofmt_ctx->opaque)[j + 3] == 0x01 && 
			((uint8_t *)ofmt_ctx->opaque)[j + 4] != 0x68)
		{
			AVPacket *pkt = av_packet_alloc();
			av_init_packet(pkt);
			pkt->size = j - frame_header;
			pkt->data = (uint8_t *)av_malloc(pkt->size);
			memcpy(pkt->data, ((uint8_t *)ofmt_ctx->opaque) + frame_header, pkt->size);
			//认为带SPS信息的为关键帧。若不正确标记关键帧,则不能正常随机定位或快进快退。
			if (((uint8_t *)ofmt_ctx->opaque)[frame_header + 4] == 0x67)
			{
				pkt->flags |= AV_PKT_FLAG_KEY;
			}
			AVRational time_base = ofmt_ctx->streams[0]->time_base;
			//Duration between 2 frames (μs)
			int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(ofmt_ctx->streams[0]->r_frame_rate);
			pkt->pts = (double)(frame_index*calc_duration) / (double)(av_q2d(time_base)*AV_TIME_BASE);
			pkt->duration = (double)calc_duration / (double)(av_q2d(time_base)*AV_TIME_BASE);
			printf("Write 1 Packet. size:%5d\tpts:%lld\n", pkt->size, pkt->pts);
			if (av_interleaved_write_frame(ofmt_ctx, pkt) < 0)
			{
				printf("Error muxing packet\n");
			}
			av_packet_free(&pkt);
			frame_index++;
			frame_header = j;
		}
		else
		{
			continue;
		}
	}
	residue_len = residue_len + buffer_size - frame_header;
	//即使有重叠区域,也允许这样进行内存拷贝,要拷贝的尾部数据会覆盖(uint8_t *)ofmt_ctx->opaque+frame_header以后的重叠区域。
	memcpy((uint8_t *)ofmt_ctx->opaque, (uint8_t *)ofmt_ctx->opaque + frame_header, residue_len);

	return ofmt_ctx;
}

int format_close(void *fmt_ctx)
{
	AVFormatContext *ofmt_ctx = (AVFormatContext *)fmt_ctx;
	
	//Write file trailer
	av_write_trailer(ofmt_ctx);

	av_free((uint8_t *)ofmt_ctx->opaque);
	if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
		avio_close(ofmt_ctx->pb);
	avformat_free_context(ofmt_ctx);

	return 0;
}
// test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdint.h>
#include <malloc.h>
#include "H264ToContainer_Win32.h"
#define BUFFER_SIZE 32768

FILE *fp_write;

int callback(void *opaque, uint8_t *buf, int buf_size)
{
	fwrite(buf, 1, buf_size, fp_write);
	return buf_size;
}

int _tmain(int argc, _TCHAR* argv[])
{
	const char *in_filename_v = "media files/JINUSEAN_17s.h264"; //Input file URL
	//const char *out_filename = "media files/JINUSEAN_17s.mkv"; //Output file URL
	fp_write = fopen("media files/out_stream.mpg", "wb+"); //输出文件  
	const char *format_name = "mpg";
    float r_frame_rate = 29.97;
	void *ofmt_ctx = format_initialise(format_name, r_frame_rate, BUFFER_SIZE, callback);
	FILE *fp_read = fopen(in_filename_v, "rb+");
	uint8_t *buf = (uint8_t *)malloc(BUFFER_SIZE);
	for (int i = 0; i < 200; i++)
	{
		fread(buf, 1, BUFFER_SIZE, fp_read);
		ofmt_ctx = format_data_import(ofmt_ctx, buf, BUFFER_SIZE);
	}
	free(buf);
	format_close(ofmt_ctx);
	fclose(fp_read);
	fclose(fp_write);
	
	return 0;
}


AVRational r_frame_rate -> float r_frame_rate

AVPacket pkt;

av_init_packet(&pkt);

av_free_packet(&pkt);

改为:

AVPacket *pkt = av_packet_alloc();

av_init_packet(pkt);

av_packet_free(&pkt);


该版本输出的是流而不是文件,不适用于mp4、3gp等格式。
在写文件头时,
//Write file header
if (avformat_write_header(ofmt_ctx, NULL) < 0) 
{
    printf("Error occurred when opening output file\n");
    goto end;
}
报错:
[mp4 @ 04b86e60] muxer does not support non seekable output
[3gp @ 03a66e60] muxer does not support non seekable output
Error occurred when opening output file

You can't use MP4 with STDOUT. 
The MP4 container requires the encoder to go back and make changes to the beginning of the file after it is done writing through the end. 
Since STDOUT cannot seek, outputting media in an MP4 container is not possible with STDOUT.

some people are saying that mp4 simply cannot be streamed.

命令行下通过设置参数,可以输出到pipe

pipe
ffmpeg -i foo.mov -f mp4 - 

Use either -frag_duration or -frag_size to allow writing 
> mov (mp4) files to a pipe.

之前版本输出文件时:

能跳跃播放:mkv mp4 flv asf avi
 
不能跳跃播放:h264 ps ts 

该版本输出流时:

能跳跃播放:flv

不能跳跃播放:h264 ps ts mkv asf avi 

猜你喜欢

转载自blog.csdn.net/u013699869/article/details/50266335