tstools封裝H264+aac成TS文件

本例修改Tstools的esmerge.c示例 部分更改庫內容,完成讀取H264文件和AAC文件封裝成TS文件。 

使用函數指針做參數傳遞,達到把讀取源文件和寫目標文件方法提到更好操作的地方。


1. 下載tstools源碼

    官方網站: http://tstool.sourceforge.net/

    CSDN資源下載:http://download.csdn.net/download/u011298831/9596746


2. 編譯

    源碼編譯時我出現了錯誤

    - LIBOPTS = -L$(LIBDIR) -ltstools $(ARCH_FLAGS)

   + LIBOPTS = -L$(LIBDIR) -ltstools $(ARCH_FLAGS) -lm

   修改Makefile之後即可正常編譯


3. 參照esmerge.c添加自己的esmerge_plus.c

    Makefile中參考esmerge的方法添加esmerge_plus.c的方法

      $(OBJDIR)/esmerge.o \
    +$(OBJDIR)/esmerge_plus.o \


      $(BINDIR)/esmerge \
   +$(BINDIR)/esmerge_plus \


    $(BINDIR)/esmerge: $(OBJDIR)/esmerge.o $(LIB)
$(CC) $< -o $(BINDIR)/esmerge $(LDFLAGS) $(LIBOPTS)
     +$(BINDIR)/esmerge_plus: $(OBJDIR)/esmerge_plus.o $(LIB)
                $(CC) $< -o $(BINDIR)/esmerge_plus $(LDFLAGS) $(LIBOPTS)


    $(OBJDIR)/esmerge.o:     esmerge.c misc_fns.h $(ACCESSUNIT_H) $(AUDIO_H) $(TSWRITE_H) version.h
    $(CC) -c $< -o $@ $(CFLAGS)
  +$(OBJDIR)/esmerge_plus.o:     esmerge_plus.c misc_fns.h $(ACCESSUNIT_H) $(AUDIO_H) $(TSWRITE_H) version.h
    $(CC) -c $< -o $@ $(CFLAGS)


這樣執行make即可獲得bin\下的esmerge_plus生成文件


4. 正式修改庫和esmerge_plus.c文件

    源esmerge.c文件是採用傳遞文件名字和文件句柄的方式,些ts目標文件也在較爲深的地方,現在的目的是吧讀源文件的和寫目標文件提到main函數層次,方便實現read和write

    先提供main函數。

int main(int argc, char **argv)
{
	int    had_video_name = FALSE;
	int    had_audio_name = FALSE;
	int    had_output_name = FALSE;
	char  *video_name = NULL;
	char  *audio_name = NULL;
	char  *output_name = NULL;
	int    err = 0;
	ES_p   video_es = NULL;
	access_unit_context_p h264_video_context = NULL;
	avs_context_p avs_video_context = NULL;
	int    audio_file = -1;
	TS_writer_p output = NULL;
	int    quiet = FALSE;
	int    verbose = FALSE;
	int    debugging = FALSE;
	int    audio_samples_per_frame = ADTS_SAMPLES_PER_FRAME;
	int    audio_sample_rate = DAT_RATE;
	int    video_frame_rate = DEFAULT_VIDEO_FRAME_RATE;
	int    audio_type = AUDIO_ADTS;
	int    video_type = VIDEO_H264;
	int    pat_pmt_freq = 0;
	int    ii = 1;

#if TEST_PTS_DTS
	test_pts();
	return 0;
#endif

	if (argc < 2)
	{
		print_usage();
		return 0;
	}

	video_type = VIDEO_H264;
	read_file = video_name = argv[1];
	audio_read_file = audio_name = argv[2];
	write_file = output_name = argv[3];
	fprintf(stderr, "### esmerge: video_name:%s \n", video_name);
	fprintf(stderr, "### esmerge: audio_name:%s \n", audio_name);
	fprintf(stderr, "### esmerge: output_name:%s\n", output_name);

	//err = open_elementary_stream(video_name,&video_es);
	err = open_elementary_stream_ex(video_read_func, &video_es);
	if (err)
	{
		fprintf(stderr, "### esmerge: "
			"Problem starting to read video as ES - abandoning reading\n");
		return 1;
	}

	if (video_type == VIDEO_H264)
	{
		err = build_access_unit_context(video_es, &h264_video_context);
		if (err)
		{
			fprintf(stderr, "### esmerge: "
				"Problem starting to read video as H.264 - abandoning reading\n");
			close_elementary_stream(&video_es);
			return 1;
		}
	}
	else
	{
		fprintf(stderr, "### esmerge: Unknown video type\n");
		return 1;
	}

	//======================================================================
	//  audio_file = open_binary_file(audio_name,FALSE);
	//  if (audio_file == -1)
	//  {
	//    fprintf(stderr,"### esmerge: "
	//            "Problem opening audio file - abandoning reading\n");
	//    close_elementary_stream(&video_es);
	//    free_access_unit_context(&h264_video_context);
	//    free_avs_context(&avs_video_context);
	//    return 1;
	//  }
	//======================================================================

	//err = tswrite_open(TS_W_FILE,output_name,NULL,0,quiet,&output);
	err = tswrite_open_ex(TS_W_CALL, video_write_func, NULL, 0, quiet, &output);
	if (err)
	{
		fprintf(stderr, "### esmerge: "
			"Problem opening output file %s - abandoning reading\n",
			output_name);
		close_elementary_stream(&video_es);
		close_file(audio_file);
		free_access_unit_context(&h264_video_context);
		free_avs_context(&avs_video_context);
		return 1;
	}

	switch (audio_type)
	{
	case AUDIO_ADTS:
		audio_samples_per_frame = ADTS_SAMPLES_PER_FRAME;
		break;
	default:              // hmm - or we could give up...
		audio_samples_per_frame = ADTS_SAMPLES_PER_FRAME;
		break;
	}

	if (!quiet)
	{
		printf("Reading video from %s\n", video_name);
		printf("Writing output to  %s\n", output_name);
		printf("Video frame rate: %dHz\n", video_frame_rate);
	}

	if (video_type == VIDEO_H264)
		
		err = merge_with_h264(h264_video_context, audio_read_func, output,
		//err = merge_with_h264(h264_video_context, read_next_adts_frame, output,
		AUDIO_ADTS_MPEG2,
		audio_samples_per_frame, audio_sample_rate,
		video_frame_rate,
		pat_pmt_freq,
		quiet, verbose, debugging);
	else
	{
		printf("### esmerge: Unknown video type\n");
		return 1;
	}
	if (err)
	{
		printf("### esmerge: Error merging video and audio streams\n");
		close_elementary_stream(&video_es);
		free_access_unit_context(&h264_video_context);
		(void)tswrite_close(output, quiet);
		return 1;
	}

	close_elementary_stream(&video_es);
	free_access_unit_context(&h264_video_context);
	err = tswrite_close(output, quiet);
	if (err)
	{
		printf("### esmerge: Error closing output %s\n", output_name);
		return 1;
	}
	return 0;
}
本例只支持baseline/main profile的H264和-mp2adts AAC文件,讀者自己根據源碼做相應修改。

兩個新添加的函數:

err = open_elementary_stream_ex(video_read_func, &video_es);

err = tswrite_open_ex(TS_W_CALL, video_write_func, NULL, 0, quiet, &output);


1)open_elementary_stream_ex  (es.c跟蹤open_elementary_stream函數添加以下代碼)

extern int open_elementary_stream_ex(int  (*callreadfun)(char*, int),
                                  ES_p  *es)
{
  int err;
  int input;

  input = -1;
  
  err = build_elementary_stream_file_ex(callreadfun,es);
  if (err)
  {
    fprintf(stderr,"### build_elementary_stream_file_ex ");
    return 1;
  }
  return 0;
}


同上,在相應位置添加build_elementary_stream_file_ex(es.c)方法
 extern int build_elementary_stream_file_ex(int  (*callreadfun)(char*, int),
                                        ES_p  *es)
{
  ES_p new = malloc(SIZEOF_ES);
  if (new == NULL)
  {
    fprintf(stderr,"### Unable to allocate elementary stream datastructure\n");
    return 1;
  }

  new->self_input = 1;
  new->callreadfun = callreadfun;
  new->reading_ES = TRUE;
  new->input = -1;
  new->reader = NULL;
  //fprintf(stderr,"===>> setup_readahead new->callreadfun:%x\n",new->callreadfun);
  setup_readahead(new);

  *es = new;
  return 0;
}
這裏出現兩個ES_p結構原本沒有的成員:self_input和callreadfun

struct elementary_stream
{
  int       reading_ES;  // TRUE if we're reading ES data direct, FALSE if PES

  +//self define read
  +int self_input;
  +int (*callreadfun)(char* data, int* len);

  ……
};

如上,在結構體elementary_stream(es_defns.h)中添加了self_input和 callreadfun,用作描述是否是個人定義的方式和傳遞讀數據的函數方法


2)tswrite_open_ex(TS_W_CALL, video_write_func, NULL, 0, quiet, &output); (tswrite.c 跟蹤tswrite_open函數添加以下代碼)

extern int tswrite_open_ex(TS_WRITER_TYPE  how,
                        void (*callfuct)(char*,int),
                        char           *multicast_if,
                        int             port,
                        int             quiet,
                        TS_writer_p    *tswriter)
{
	TS_writer_p  new;
	int err = tswrite_build(how,quiet,tswriter);
	if (err) return 1;

	new = *tswriter;
	switch (how)
	{
	case TS_W_CALL:	
		if (!quiet) printf("TS_W_CALL\n");
		new->where.callfun = callfuct;
	break;
	default:
		printf("### Unexpected writer type %d to tswrite_open()\n",how);
		free(new);
		return 1;
	}
	return 0;
}
同1)出現TS_writer_p結構出現where.callfun新的結構
union TS_writer_output
{
  FILE   *file;
  SOCKET  socket;
  //for ts use
  +void (*callfun)(char*,int);

};

如上,在聯合體TS_writer_output(tswrite_defns.h)中添加了 callfun,寫數據的函數方法


讀者應該對tstools有一定的熟悉,瞭解其工作流程。所以對tswrite_write方法 tswrite_close_file方法 static inline int get_more_data(ES_p  es)方法需要修改

下面給我我修改後的static inline int get_more_data(ES_p  es)方法(es.c文件中)

static inline int get_more_data(ES_p  es)
{
  if (es->reading_ES)
  {
    if(es->self_input == 1)
	{
		int len = 0;
		int ret = es->callreadfun(es->read_ahead,&len);
		//printf("### Error reading next bytes: %d\n",len);
		if (ret == 0)
			return EOF;
		else if (ret == -1)
		{
			printf("### Error reading next bytes: %s\n",strerror(errno));
			return 1;
		}
		
		es->read_ahead_posn += es->read_ahead_len;  // length of the *last* buffer
		es->read_ahead_len = len;
		es->data = es->read_ahead;     // should be done in the setup function
		es->data_end = es->data + len; // one beyond the last byte
		es->data_ptr = es->data;
	}
	else
	{
	// Call `read` directly - we don't particularly mind if we get a "short"
	// read, since we'll just catch up later on
#ifdef _WIN32
		int len = _read(es->input,&es->read_ahead,ES_READ_AHEAD_SIZE);
#else
		ssize_t  len = read(es->input,&es->read_ahead,ES_READ_AHEAD_SIZE);
#endif
		printf("### Error reading next bytes: %d\n",len);
		if (len == 0)
			return EOF;
		else if (len == -1)
		{
			printf("### Error reading next bytes: %s\n",strerror(errno));
			return 1;
		}

		es->read_ahead_posn += es->read_ahead_len;  // length of the *last* buffer
		es->read_ahead_len = len;
		es->data = es->read_ahead;     // should be done in the setup function
		es->data_end = es->data + len; // one beyond the last byte
		es->data_ptr = es->data;
	}

    return 0;
  }
  else
  {
    return get_next_pes_packet(es);
  }
}
tswrite_write方法中,爲了區分出是我們新定義的方式,我們使用TS_W_CALL這個值(tswrite.c文件中)
switch (tswriter->how)

{

……

+case TS_W_CALL:
+ tswriter->where.callfun(packet,TS_PACKET_SIZE);
        +break;

……

}

tswrite_close_file方法中,爲了區分出是我們新定義的方式,我們使用TS_W_CALL這個值(tswrite.c文件中)

switch (tswriter->how)

{

……

  +case TS_W_CALL:
  + break;

……

}

TS_W_CALL所加在的地方,查找TS_W_FILE即可

enum TS_writer_type
{
  TS_W_UNDEFINED,
  TS_W_STDOUT,  // standard output
  TS_W_FILE,    // a file
  TS_W_TCP,     // a socket, over TCP/IP
  TS_W_UDP,     // a socket, over UDP
  +TS_W_CALL,    // self define
};

如上,在枚舉TS_writer_type(tswrite_defns.h)中添加了 TS_W_CALL


就這樣,對原庫代碼的修改完成,回到esmerge_plus.c文件中。


3)merge_with_h264方法

static int merge_with_h264(access_unit_context_p  video_context,
	int(*callbackfun)(char*, int*),
	TS_writer_p            output,
	int                    audio_type,
	int                    audio_samples_per_frame,
	int                    audio_sample_rate,
	int                    video_frame_rate,
	int                    pat_pmt_freq,
	int                    quiet,
	int                    verbose,
	int                    debugging)
{
	int  ii;
	int  err;
	uint32_t prog_pids[2];
	byte     prog_type[2];

	int video_frame_count = 0;
	int audio_frame_count = 0;

	uint32_t video_pts_increment = 90000 / video_frame_rate;
	uint32_t audio_pts_increment = (90000 * audio_samples_per_frame) / audio_sample_rate;
	uint64_t video_pts = 0;
	uint64_t audio_pts = 0;

	// The "actual" times are just for information, so we aren't too worried
	// about accuracy - thus floating point should be OK.
	double audio_time = 0.0;
	double video_time = 0.0;

	int got_video = TRUE;
	int got_audio = TRUE;

	if (verbose)
		printf("Video PTS increment %u\n"
		"Audio PTS increment %u\n", video_pts_increment, audio_pts_increment);

	// Start off our output with some null packets - this is in case the
	// reader needs some time to work out its byte alignment before it starts
	// looking for 0x47 bytes
	for (ii = 0; ii < 8; ii++)
	{
		err = write_TS_null_packet(output);
		if (err) return 1;
	}

	// Then write some program data
	// @@@ later on we might want to repeat this every so often
	prog_pids[0] = DEFAULT_VIDEO_PID;
	prog_pids[1] = DEFAULT_AUDIO_PID;
	prog_type[0] = AVC_VIDEO_STREAM_TYPE;

	switch (audio_type)
	{
	case AUDIO_ADTS:
	case AUDIO_ADTS_MPEG2:
	case AUDIO_ADTS_MPEG4:
		prog_type[1] = ADTS_AUDIO_STREAM_TYPE;
		break;
	case AUDIO_L2:
		prog_type[1] = MPEG2_AUDIO_STREAM_TYPE;
		break;
	case AUDIO_AC3:
		prog_type[1] = ATSC_DOLBY_AUDIO_STREAM_TYPE;
		break;
	default:              // what else can we do?
		prog_type[1] = ADTS_AUDIO_STREAM_TYPE;
		break;
	}
	err = write_TS_program_data2(output,
		1, // transport stream id
		1, // program number
		DEFAULT_PMT_PID,
		DEFAULT_VIDEO_PID,  // PCR pid
		2, prog_pids, prog_type);
	if (err)
	{
		fprintf(stderr, "### Error writing out TS program data\n");
		return 1;
	}

	while (got_video
		|| got_audio
		)
	{
		access_unit_p  access_unit;
		audio_frame_p  aframe;

		// Start with a video frame
		if (got_video)
		{
			err = get_next_h264_frame(video_context, quiet, debugging, &access_unit);
			if (err == EOF)
			{
				if (verbose)
					fprintf(stderr, "EOF: no more video data\n");
				got_video = FALSE;
			}
			else if (err)
			{
				fprintf(stderr, "EOF: no more video data return 1;\n");
				return 1;
			}

		}

		if (got_video)
		{
			video_time = video_frame_count / (double)video_frame_rate;
			video_pts += video_pts_increment;
			video_frame_count++;
			if (verbose)
				printf("\n%s video frame %5d (@ %.2fs, " LLU_FORMAT ")\n",
				(is_I_or_IDR_frame(access_unit) ? "**" : "++"),
				video_frame_count, video_time, video_pts);

			if (pat_pmt_freq && !(video_frame_count % pat_pmt_freq))
			{
				if (verbose)
				{
					printf("\nwriting PAT and PMT (frame = %d, freq = %d).. ",
						video_frame_count, pat_pmt_freq);
				}
				err = write_TS_program_data2(output,
					1, // tsid
					1, // Program number
					DEFAULT_PMT_PID,
					DEFAULT_VIDEO_PID, // PCR pid
					2, prog_pids, prog_type);
			}


			// PCR counts frames as seen in the stream, so is easy
			// The presentation and decoding time for B frames (if we ever get any)
			// could reasonably be the same as the PCR.
			// The presentation and decoding time for I and IDR frames is unlikely to
			// be the same as the PCR (since frames come out later...), but it may
			// work to pretend the PTS is the PCR plus a delay time (for decoding)...

			// We could output the timing information every video frame,
			// but might as well only do it on index frames.
			if (is_I_or_IDR_frame(access_unit))
				err = write_access_unit_as_TS_with_pts_dts(access_unit, video_context,
				output, DEFAULT_VIDEO_PID,
				TRUE, video_pts + 45000,
				TRUE, video_pts);
			else
				err = write_access_unit_as_TS_with_PCR(access_unit, video_context,
				output, DEFAULT_VIDEO_PID,
				video_pts, 0);
			if (err)
			{
				free_access_unit(&access_unit);
				fprintf(stderr, "### Error writing access unit (frame)\n");
				return 1;
			}
			free_access_unit(&access_unit);

			// Did the logical video stream end after the last access unit?
			if (video_context->end_of_stream)
			{
				if (verbose)
					printf("Found End-of-stream NAL unit\n");
				got_video = FALSE;
			}
		}
		//continue;

		if (!got_audio || callbackfun == NULL)
			continue;

		// Then output enough audio frames to make up to a similar time
		while (audio_pts < video_pts || !got_video)
		{
			//err = read_next_audio_frame(audio_file,audio_type,&aframe);
			char aframe_buf[1024];
			int ret = callbackfun(aframe_buf, 1024);
			fprintf(stderr, "callbackfun audio data ret:%d\n", ret);
			if (ret <= 0)
			{
				got_audio = FALSE;
				break;
			}

			audio_time = audio_frame_count *
				audio_samples_per_frame / (double)audio_sample_rate;
			audio_pts += audio_pts_increment;
			audio_frame_count++;
			if (verbose)
				printf("** audio frame %5d (@ %.2fs, " LLU_FORMAT ")\n",
				audio_frame_count, audio_time, audio_pts);

			err = write_ES_as_TS_PES_packet_with_pts_dts(output, aframe_buf,
				ret,
				DEFAULT_AUDIO_PID,
				DEFAULT_AUDIO_STREAM_ID,
				TRUE, audio_pts,
				TRUE, audio_pts);
			if (err)
			{
				return 1;
			}
		}
	}

	if (!quiet)
	{
		uint32_t video_elapsed = 100 * video_frame_count / video_frame_rate;
		uint32_t audio_elapsed = 100 * audio_frame_count*
			audio_samples_per_frame / audio_sample_rate;
		printf("Read %d video frame%s, %.2fs elapsed (%dm %.2fs)\n",
			video_frame_count, (video_frame_count == 1 ? "" : "s"),
			video_elapsed / 100.0, video_elapsed / 6000, (video_elapsed % 6000) / 100.0);
		printf("Read %d audio frame%s, %.2fs elapsed (%dm %.2fs)\n",
			audio_frame_count, (audio_frame_count == 1 ? "" : "s"),
			audio_elapsed / 100.0, audio_elapsed / 6000, (audio_elapsed % 6000) / 100.0);
	}

	return 0;

}


然後是幾個讀寫方法

#include <fcntl.h>
int video_fd_r = -1;
int video_fd_w = -1;
int audio_fd_r = -1;

char* read_file = NULL;
char* write_file = NULL;
char* audio_read_file = NULL;

int video_write_func(char* data, int len)
{
	if (video_fd_w == -1)
	{
		int flags = 0;
		flags = flags | O_WRONLY | O_CREAT | O_TRUNC;
		video_fd_w = open(write_file, flags, 00777);

		if (video_fd_w == -1)
		{
			fprintf(stderr, "### Error opening file %s %s\n", write_file, strerror(errno));
		}
		else
		{
			fprintf(stderr, "### opening file %s %d\n", write_file, video_fd_w);
		}
	}

	int _len = write(video_fd_w, data, len);
	if (_len == len)
	{
		//fprintf(stderr,"====>>> video_write_func _len:%d len:%d\n",_len,len);
	}
	else
	{
		//fprintf(stderr,"### error write file %s %s\n","test.ts",strerror(errno));	
	}

}

int audio_read_func(char* data, int len)
{
	if (audio_fd_r == -1)
	{
		int flags = 0;
		flags = flags | O_RDONLY;
		audio_fd_r = open(audio_read_file, flags);

		if (audio_fd_r == -1)
		{
			fprintf(stderr, "###audio_read_func Error opening file %s %s\n", audio_read_file, strerror(errno));
		}
		else
		{
			fprintf(stderr, "###audio_read_func opening file %s %d\n", audio_read_file, audio_fd_r);
		}
	}

	unsigned char aac_header[7];
	int true_size = 0;

	true_size = read(audio_fd_r, aac_header, 7);
	if (true_size <= 0)
	{
		return 0;
	}
	else
	{
		int frame_length = ((aac_header[3] & 0x03) << 11) | (aac_header[4] << 3) |
      						((unsigned)(aac_header[5] & 0xE0) >> 5);

		int ii;
		for (ii=0; ii<6; ii++)
    		data[ii] = aac_header[ii];

		true_size = read(audio_fd_r, &(data[7]), frame_length - 7);
		return frame_length;
	}

}

int video_read_func(char* data, int* len)
{
	if (video_fd_r == -1)
	{
		int flags = 0;
		flags = flags | O_RDONLY;
		video_fd_r = open(read_file, flags);

		if (video_fd_r == -1)
		{
			fprintf(stderr, "### Error opening file %s %s\n", read_file, strerror(errno));
		}
		else
		{
			fprintf(stderr, "### opening file %s %d\n", read_file, video_fd_r);
		}
	}
	*len = read(video_fd_r, data, ES_READ_AHEAD_SIZE);
	if (*len > 0)
	{
		//fprintf(stderr,"====>>> callreadfun len:%d\n",*len);
	}
	else if (*len < 0)
	{
		//fprintf(stderr,"### error read file %s %s\n","test.264",strerror(errno));	
	}
	return *len;
}


搞定!記得修改幾個宏定義和初始化的值;

#define DEFAULT_VIDEO_FRAME_RATE  30

int    audio_sample_rate = DAT_RATE; //我用的是48000Hz的

最後聲明:本例只支持baseline/main profile的H264和-mp2adts AAC文件,讀者自己根據源碼做相應修改。

附上我的源碼:http://download.csdn.net/download/u011298831/9596805


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章