1、README
a. demo使用
$ make clean && make DEBUG=1
$
$ ./ts_mux_h264_aac
Usage:
./ts_mux_h264_aac avfile/test1_856x480_24fps.h264 24 avfile/test1_44100_stereo.aac out1.ts
./ts_mux_h264_aac avfile/test2_720x480_30fps.h264 60 avfile/test2_48000_stereo.aac out2.ts
./ts_mux_h264_aac avfile/test3_1280x720_20fps.h264 20 avfile/test1_44100_stereo.aac out3.ts
(注:目前合成的out1.ts在使用potPlayer播放时进度条有点问题,待修复。)
目前合成视频的现象:
(out2.ts含有除SPS、PPS、IDR、PSLICE以外的其他NALU,所以主要看out1.ts和out3.ts)
-
out1.ts:
- 电影和电视、ACG播放器
- 播放不流畅(含有B帧,而程序只填pts没填dts导致,对比out3.ts可知,无关紧要);
- 正常播放进度条时长正常(29s),拖动后有声音但画面卡住一会才动、音视频同步;
- VLC
- 播放不流畅(含有B帧,而程序只填pts没填dts导致,对比out3.ts可知,无关紧要);
- 正常播放进度条时长正常(29s),拖动后有声音但画面会灰屏有画面在动过一会才恢复、音视频同步;
- potplayer
- 播放流畅;
- 正常播放时进度条时长不正常(21s),拖动画面不会卡住、但音视频有时不同步;
- 电影和电视、ACG播放器
-
out3.ts
- 电影和电视、ACG播放器
- 播放流畅;
- 正常播放进度条时长正常(30s),拖动后有声音但画面卡住一会才动、音视频“同步”;
- VLC
- 播放流畅;
- 正常播放进度条时长正常(30s),拖动后有声音但画面会灰屏有画面在动过一会才恢复、音视频“同步”;
- potplayer
- 播放流畅;
- 正常播放时进度条时长不正常(8s),拖动画面和声音都会卡住一会才动;
- 电影和电视、ACG播放器
b. 参考文章
-
【科普】TS文件格式:什么是TS?如何打开和转换它?_都叫兽软件
-
TS封装格式 - CrazyDiode - 博客园(推荐!!看这篇基本就够了!!!其余文章作为参考即可。)
-
TS文件格式_LaugustusJ的博客-****博客_ts文件(没仔细看)
-
TS 文件格式解析_影音视频技术-****博客_ts格式解析(没仔细看)
-
测试验证:h264实时流封装ts文件存储,完整实现 - jamin-snails的个人空间 - OSCHINA - 中文开源技术交流社区(没仔细看)
工具下载:
- SpecialFTS.exe(demo中的tools/SpecialFTS_1.1.7z)【源码下载地址】
参考源码:
- https://download.****.net/download/zhuweigangzwg/5605869?spm=1003.2166.3001.6637.7(推荐!!)
- https://github.com/Jsnails/MUX_TS(根据前者进行改动的,可以不用看)
c. demo目录架构
$ tree
.
├── aac_adts.c
├── aac_adts.h
├── avfile
│ ├── out1.ts
│ ├── out2.ts
│ ├── out3.ts
│ ├── test1_44100_stereo.aac
│ ├── test1_856x480_24fps.h264
│ ├── test2_48000_stereo.aac
│ ├── test2_720x480_30fps.h264
│ └── test3_1280x720_20fps.h264
├── crcLib.c
├── crcLib.h
├── docs
│ ├── TS封装格式 - CrazyDiode - 博客园.mhtml
│ ├── TS文件格式_LaugustusJ的博客-****博客_ts文件.mhtml
│ ├── TS 文件格式解析_影音视频技术-****博客_ts格式解析.mhtml
│ ├── 测试验证:h264实时流封装ts文件存储,完整实现 - jamin-snails的个人空间 - OSCHINA - 中文开源技术交流社区.mhtml
│ └── 【科普】TS文件格式:什么是TS?如何打开和转换它?_都叫兽软件.mhtml
├── h264_nalu.c
├── h264_nalu.h
├── main.c
├── Makefile
├── README.md
├── reference_src
│ ├── H264_AAC_TS_MUX.tar.bz2
│ └── MUX_TS-master.zip
├── tools
│ └── SpecialFTS_1.1.7z
├── ts.c
└── ts.h
2、主要代码片段
ts.h
#ifndef __TS_H__
#define __TS_H__
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
//#define ENABLE_DEBUG
#ifdef ENABLE_DEBUG
#define DEBUG(fmt, args...) printf(fmt, ##args)
#else
#define DEBUG(fmt, args...)
#endif
#define TS_HEADER_SYNC_BYTE 0x47
#define TS_PACKET_HEADER_SIZE 4
#define TS_PACKET_SIZE 188
/* PID: 13 bit */
#define TS_PID_PAT 0x0000 /* fixed */
#define TS_PID_PMT 0x0FFF /* custom */
#define TS_PID_H264 0x0007 /* custom */
#define TS_PID_AAC 0x0008 /* custom */
#define TS_STREAM_ID_VIDEO 0xE0 /* always */
#define TS_STREAM_ID_AUDIO 0xC0 /* always */
#define PMT_STREAM_TYPE_H264 0x1B /* fixed */
#define PMT_STREAM_TYPE_AAC 0x0F /* fixed */
#define PMT_STREAM_TYPE_MP3 0x03 /* fixed */
/* ts layer header element member.
* [Note: It is not stored as defined type size!!!]
*/
typedef struct tsHeader
{
uint8_t sync_byte; // 8b, must be 0x47
uint8_t transport_error_indicator; // 1b, transport error flag, always '0'. '1': after the adapt field of the ts head is a useless byte, this byte is in the adaptive area length.
uint8_t payload_unit_start_indicator; // 1b, the load unit starts the identifier, and a complete packet begins with a tag of '1'.
uint8_t transport_priority; // 1b, always '0'.
uint16_t pid; // 13b, packet id.
uint8_t transport_scrambling_control; // 2b, always '00'. transfer disturbance control.
uint8_t adaptation_field_control; // 2b, whether has adaption field. '00': reserved, '01': playload, '10': adaption_field, '11': playload+adaption_field.
uint8_t continuity_counter; // 4b, increasing counter, 0~f, the starting value is not necessarily 0, but it must be continuous.
}T_TsHeader, *PT_TsHeader;
/* adaptationField element member.
* [Note: It is not stored as defined type size!!!]
*/
typedef struct adaptationField
{
uint8_t adaptation_field_length; // 1B
uint8_t discontinuty_indicator; // 1b, the continuous state of the current transfer.
uint8_t random_access_indicator; // 1b, the next pes group with the same pid should contain the PTS fields and a raw flow access point.
uint8_t elementary_stream_priority_indicator; // 1b, priority
/* 5 flags for optional fields */
uint8_t pcr_flag; // 1b
uint8_t opcr_flag; // 1b
uint8_t splicing_point_flag; // 1b
uint8_t transport_private_data_flag; // 1b
uint8_t adaptation_field_extension_flag; // 1b
/* optional fields */
uint64_t pcr; // 42b, Program Clock Reference
uint64_t opcr; // 42b
uint8_t splice_countdown; // 1B
uint8_t transport_private_data_len; // 1B
uint8_t transport_private_data[256];
}T_AdaptationField, *PT_AdaptationField;
/* pts and dts element member.
* [Note: It is not stored as defined type size!!!]
*/
typedef struct tsPtsDts
{
uint8_t reserved_1; // 4b
uint8_t pts_32_30; // 3b
uint8_t marker_bit1; // 1b
uint32_t pts_29_15; // 15b
uint8_t marker_bit2; // 1b
uint32_t pts_14_0; // 15b
uint8_t marker_bit3; // 1b
uint8_t reserved_2; // 4b
uint8_t dts_32_30; // 3b
uint8_t marker_bit4; // 1b
uint32_t dts_29_15; // 15b
uint8_t marker_bit5; // 1b
uint32_t dts_14_0; // 15b
uint8_t marker_bit6; // 3b
}TsPtsDts;
/* PAT element member.
* [Note: It is not stored as defined type size!!!]
*/
typedef struct pat
{
uint8_t table_id; // 8b, PAT is fixed 0x00.
uint8_t section_syntax_indicator; // 1b, fixed '1'.
uint8_t zero; // 1b, fixed '0'.
uint8_t reserved_1; // 2b, fixed '11'.
uint16_t section_length; // 12b, length of data behind, comtain "crc32 code".
uint16_t transport_stream_id; // 16b, fixed 0x0001.
uint8_t reserved_2; // 2b, fixed '11'.
uint8_t version_number; // 5b, fixed '00000'.
uint8_t current_next_indicator; // 1b, fixed '1', the "pat" is available, '0' is wait for next "pat".
uint8_t section_number; // 8b, fixed 0x00.
uint8_t last_section_number; // 8b, fixed 0x00.
/* loop start */
uint32_t program_number; // 16b, 0x0000: NIT, 0x0001: PMT.
uint8_t reserved_3; // 3b, fixed '111'.
uint32_t pid; // 13b, program_number pid
/* loop end */
uint32_t crc_32; // 32b, the crc32 check code for the previous data
}T_Pat, *PT_Pat;
/* PMT element member.
* [Note: It is not stored as defined type size!!!]
*/
typedef struct pmt
{
uint8_t table_id; // 8b, PMT is fixed 0x02.
uint8_t section_syntax_indicator; // 1b, fixed '1'.
uint8_t zero; // 1b, fixed '0'.
uint8_t reserved_1; // 2b, fixed '11'.
uint32_t section_length; // 12b, length of data behind, comtain "crc32 code".
uint32_t program_number; // 16b, pid of applicable program.
uint8_t reserved_2; // 2b, fixed '11'.
uint8_t version_number; // 5b, fixed '00000', if pat variable will be '00001'.
uint8_t current_next_indicator; // 1b, fixed '1'.
uint8_t section_number; // 8b, fixed 0x00.
uint8_t last_section_number; // 8b, fixed 0x00.
uint8_t reserved_3; // 3b, fixed '111'.
uint32_t pcr_pid; // 13b, The pcr is grouped by the ts, which is designated as the video pid.
uint8_t reserved_4; // 4b, fixed '1111'.
uint32_t program_info_length; // 12b, program describes, 0x000 for no.
/* loop start */
/* program 1 */
uint8_t stream_type_video; // 8b, stream type. 0x1b: h264 0x0f: aac 0x03: mp3
uint8_t reserved_5_video; // 3b, fixed '111'
uint32_t elementary_pid_video; // 13b, pid of "stream type"
uint8_t reserved_6_video; // 4b, fixed '1111'
uint32_t es_info_length_video; // 12b, program describes, 0x000 for no.
/* program 2 */
uint8_t stream_type_audio;
uint8_t reserved_5_audio;
uint32_t elementary_pid_audio;
uint8_t reserved_6_audio;
uint32_t es_info_length_audio;
/* loop end */
uint32_t crc_32; // 32b, the crc32 check code for the previous data
}T_Pmt, *PT_Pmt;
/* custom "PES" structure, it includes "pes header" and "es data".
* so the member's size is different in ts file.
*/
typedef struct
{
/* "pes header"(not contain pts/dts) : 9 Bytes */
uint32_t packet_start_code_prefix; // 3B, start code, must be 0x000001.
uint8_t stream_id; // 1B, audio(0xc0~0xdf), always 0xc0; video(0xe0~0xef), always 0xe0.
uint32_t pes_packet_length; // 2B, the length of the data behind, 0 indicates unlimited length.
// 1B: always 0x80 in this Byte.
uint8_t marker_bit; // 2b, must be '10'.
uint8_t pes_scrambling_control; // 2b, pes packet scrambling control.
uint8_t pes_priority; // 1b, pes packet priority.
uint8_t data_alignment_indicator; // 1b, '1': the follows video or audio syncword is shown in the back of the pes package.
uint8_t copyright; // 1b, copyright protection.
uint8_t original_or_copy; // 1b, playload source.
// 1B: 0x80: only pts; 0xc0: pts+dts
uint8_t pts_dts_flags; // 2b, '10': PTS; '11': PTS+DTS, '00': none, '01': reserved.
uint8_t escr_flag; // 1b, '1': escr_base+escr_expand; '0': none.
uint8_t es_rate_flag; // 1b, es rate.
uint8_t dsm_trick_mode_flag; // 1b, whether the 8 bitt connection field exists.
uint8_t additional_copy_info_flag; // 1b, additional copy information.
uint8_t pes_crc_flag; // 1b, whether there is a call.
uint8_t pes_extension_flag; // 1b, extension.
uint8_t pes_data_length; // 1B, 5 or 10, the length of the data(pts, dts) behind.
TsPtsDts ts_pts_dts; // pts+dts structure.
/* "es data": 1024*1024+4 Bytes max */
//uint8_t av_data[MAX_ONE_FRAME_SIZE]; // one frame audio or video data.
uint8_t* av_data; // one frame audio or video data.
uint8_t* av_data_cur_ptr; // position for current "av_data"
uint32_t av_data_len; // length of data.
}T_PesEsStruct, *PT_PesEsStruct;
/************************************************************************
* function describe: Mux h264 and aac to TS file.
* params:
* [h264FileName]: h264 file.(in)
* [vFps]: video fps.(in)
* [aacFileName]: aac file file.(in)
* [tsFileName]: ts file.(in)
* return: 0-success other-error
************************************************************************/
int ts_mux_h264_aac(char *h264FileName, uint32_t vFps, char *aacFileName, char *tsFileName);
#endif
ts.c
#include "h264_nalu.h"
#include "aac_adts.h"
#include "ts.h"
#include "crcLib.h"
static uint32_t endian_convert(uint32_t val)
{
return (((val >> 0) & 0xff) << 24) |\
(((val >> 8) & 0xff) << 16) |\
(((val >> 16) & 0xff) << 8) |\
(((val >> 24) & 0xff) << 0);
}
static int fixFirstPacketAdaptionFieldStruct(T_AdaptationField *ptAdaptationField, uint64_t pts)
{
if(!ptAdaptationField)
{
printf("[%s:%d] Params invalid!\n", __FUNCTION__, __LINE__);
return -1;
}
ptAdaptationField->adaptation_field_length = 7; /* pcr_flag = 1: has pcr */
ptAdaptationField->discontinuty_indicator = 0;
ptAdaptationField->random_access_indicator = 0; /* is keyflame? always 0, you can set it such "iskeyflame?1:0" */
ptAdaptationField->elementary_stream_priority_indicator = 0;
/* 5 flags for optional fields */
ptAdaptationField->pcr_flag = 1;
ptAdaptationField->opcr_flag = 0;
ptAdaptationField->splicing_point_flag = 0;
ptAdaptationField->transport_private_data_flag = 0;
ptAdaptationField->adaptation_field_extension_flag = 0;
ptAdaptationField->pcr = pts * 300;
ptAdaptationField->opcr = 0;
ptAdaptationField->splice_countdown = 0;
ptAdaptationField->transport_private_data_len = 0;
return 0;
}
static int fixCommonAdaptionFieldStruct(T_AdaptationField *ptAdaptationField)
{
if(!ptAdaptationField)
{
printf("[%s:%d] Params invalid!\n", __FUNCTION__, __LINE__);
return -1;
}
ptAdaptationField->adaptation_field_length = 1; /* 5 flags = 0 */
ptAdaptationField->discontinuty_indicator = 0;
ptAdaptationField->random_access_indicator = 0; /* is keyflame? always 0, you can set it such "iskeyflame?1:0" */
ptAdaptationField->elementary_stream_priority_indicator = 0;
/* 5 flags for optional fields */
ptAdaptationField->pcr_flag = 0;
ptAdaptationField->opcr_flag = 0;
ptAdaptationField->splicing_point_flag = 0;
ptAdaptationField->transport_private_data_flag = 0;
ptAdaptationField->adaptation_field_extension_flag = 0;
ptAdaptationField->pcr = 0;
ptAdaptationField->opcr = 0;
ptAdaptationField->splice_countdown = 0;
ptAdaptationField->transport_private_data_len = 0;
return 0;
}
static int fixAdtsFrameToPesStruct(uint8_t *adtsData, uint32_t adtsDataLen, uint64_t pts, T_PesEsStruct *ptAdtsPesStu)
{
if(!ptAdtsPesStu || !adtsData || !adtsDataLen)
{
printf("[%s:%d] Params invalid!\n", __FUNCTION__, __LINE__);
return -1;
}
/* pes header */
ptAdtsPesStu->packet_start_code_prefix = 0x000001;
ptAdtsPesStu->stream_id = TS_STREAM_ID_AUDIO;
ptAdtsPesStu->pes_packet_length = 0;
ptAdtsPesStu->marker_bit = 0b10;
ptAdtsPesStu->pes_scrambling_control = 0b00;
ptAdtsPesStu->pes_priority = 0b0;
ptAdtsPesStu->data_alignment_indicator = 0b0;
ptAdtsPesStu->copyright = 0b0;
ptAdtsPesStu->original_or_copy = 0b0;
ptAdtsPesStu->pts_dts_flags = 0b10; // '10': audio(aac) only PTS
ptAdtsPesStu->escr_flag = 0b0;
ptAdtsPesStu->es_rate_flag = 0b0;
ptAdtsPesStu->dsm_trick_mode_flag = 0b0;
ptAdtsPesStu->additional_copy_info_flag = 0b0;
ptAdtsPesStu->pes_crc_flag = 0b0;
ptAdtsPesStu->pes_extension_flag = 0b0;
ptAdtsPesStu->pes_data_length = 5; // pts_dts_flags = '10': audio(aac) only PTS
ptAdtsPesStu->ts_pts_dts.reserved_1 = 0b1111;
if(pts > 0x7FFFFFFF)
{
ptAdtsPesStu->ts_pts_dts.pts_32_30 = (pts >> 30) & 0x07;
ptAdtsPesStu->ts_pts_dts.marker_bit1 = 0b1;
}
else
{
ptAdtsPesStu->ts_pts_dts.pts_32_30 = 0;
ptAdtsPesStu->ts_pts_dts.marker_bit1 = 0b0;
}
if(pts > 0x7FFF)
{
ptAdtsPesStu->ts_pts_dts.pts_29_15 = (pts >> 15) & 0x007FFF;
ptAdtsPesStu->ts_pts_dts.marker_bit2 = 0b1;
}
else
{
ptAdtsPesStu->ts_pts_dts.pts_29_15 = 0;
ptAdtsPesStu->ts_pts_dts.marker_bit2 = 0b0;
}
ptAdtsPesStu->ts_pts_dts.pts_14_0 = pts & 0x007FFF;
ptAdtsPesStu->ts_pts_dts.marker_bit3 = 0b1;
/* es data */
ptAdtsPesStu->av_data = adtsData;
ptAdtsPesStu->av_data_len = adtsDataLen;
return 0;
}
int fixH264FrameToPesStruct(uint8_t *h264Data, uint32_t h264DataLen, uint64_t pts, T_PesEsStruct* ptH264PesStu)
{
if(!ptH264PesStu || !h264Data || !h264DataLen)
{
printf("[%s:%d] Params invalid!\n", __FUNCTION__, __LINE__);
return -1;
}
/* pes header */
ptH264PesStu->packet_start_code_prefix = 0x000001;
ptH264PesStu->stream_id = TS_STREAM_ID_VIDEO;
ptH264PesStu->pes_packet_length = 0;
ptH264PesStu->marker_bit = 0b10;
ptH264PesStu->pes_scrambling_control = 0b00;
ptH264PesStu->pes_priority = 0b0;
ptH264PesStu->data_alignment_indicator = 0b0;
ptH264PesStu->copyright = 0b0;
ptH264PesStu->original_or_copy = 0b0;
ptH264PesStu->pts_dts_flags = 0b10; // '10': only PTS
ptH264PesStu->escr_flag = 0b0;
ptH264PesStu->es_rate_flag = 0b0;
ptH264PesStu->dsm_trick_mode_flag = 0b0;
ptH264PesStu->additional_copy_info_flag = 0b0;
ptH264PesStu->pes_crc_flag = 0b0;
ptH264PesStu->pes_extension_flag = 0b0;
ptH264PesStu->pes_data_length = 5; // pts_dts_flags = '10'
ptH264PesStu->ts_pts_dts.reserved_1 = 0x03;
if(pts > 0x7FFFFFFF)
{
ptH264PesStu->ts_pts_dts.pts_32_30 = (pts >> 30) & 0x07;
ptH264PesStu->ts_pts_dts.marker_bit1 = 0b1;
}
else
{
ptH264PesStu->ts_pts_dts.pts_32_30 = 0;
ptH264PesStu->ts_pts_dts.marker_bit1 = 0b0;
}
if(pts > 0x7FFF)
{
ptH264PesStu->ts_pts_dts.pts_29_15 = (pts >> 15) & 0x007FFF ;
ptH264PesStu->ts_pts_dts.marker_bit2 = 0b1;
}
else
{
ptH264PesStu->ts_pts_dts.pts_29_15 = 0;
ptH264PesStu->ts_pts_dts.marker_bit2 = 0b0;
}
ptH264PesStu->ts_pts_dts.pts_14_0 = pts & 0x007FFF;
ptH264PesStu->ts_pts_dts.marker_bit3 = 0b1;
/* es data */
ptH264PesStu->av_data = h264Data;
ptH264PesStu->av_data_len = h264DataLen;
return 0;
}
static int fixFirstPacketAdaptionField(T_AdaptationField *adaptationField, uint8_t adaptFieldLen, uint8_t *adaptBuf)
{
uint8_t adaptiveFlags = 0x00;
uint8_t adaptivePos = 1; //