mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
for #293, support http ts stream. 2.0.101
This commit is contained in:
parent
dfe385d0c9
commit
2a05783d5c
7 changed files with 419 additions and 375 deletions
|
@ -51,369 +51,23 @@ using namespace std;
|
|||
#include <srs_kernel_file.hpp>
|
||||
#include <srs_protocol_buffer.hpp>
|
||||
|
||||
// max PES packets size to flush the video.
|
||||
#define SRS_AUTO_HLS_AUDIO_CACHE_SIZE 1024 * 1024
|
||||
|
||||
// drop the segment when duration of ts too small.
|
||||
#define SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS 100
|
||||
|
||||
// @see: NGX_RTMP_HLS_DELAY,
|
||||
// 63000: 700ms, ts_tbn=90000
|
||||
#define SRS_AUTO_HLS_DELAY 63000
|
||||
|
||||
// @see: ngx_rtmp_mpegts_header
|
||||
u_int8_t mpegts_header[] = {
|
||||
/* TS */
|
||||
0x47, 0x40, 0x00, 0x10, 0x00,
|
||||
/* PSI */
|
||||
0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
||||
/* PAT */
|
||||
0x00, 0x01, 0xf0, 0x01,
|
||||
/* CRC */
|
||||
0x2e, 0x70, 0x19, 0x05,
|
||||
/* stuffing 167 bytes */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
|
||||
/* TS */
|
||||
0x47, 0x50, 0x01, 0x10, 0x00,
|
||||
/* PSI */
|
||||
0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
||||
/* PMT */
|
||||
0xe1, 0x00,
|
||||
0xf0, 0x00,
|
||||
// must generate header with/without video, @see:
|
||||
// https://github.com/winlinvip/simple-rtmp-server/issues/40
|
||||
0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264, pid=0x100=256 */
|
||||
0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac, pid=0x101=257 */
|
||||
/*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */
|
||||
/* CRC */
|
||||
0x2f, 0x44, 0xb9, 0x9b, /* crc for aac */
|
||||
/*0x4e, 0x59, 0x3d, 0x1e,*/ /* crc for mp3 */
|
||||
/* stuffing 157 bytes */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
// @see: ngx_rtmp_mpegts.c
|
||||
// TODO: support full mpegts feature in future.
|
||||
class SrsMpegtsWriter
|
||||
{
|
||||
public:
|
||||
static int write_header(SrsFileWriter* writer)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if ((ret = writer->write(mpegts_header, sizeof(mpegts_header), NULL)) != ERROR_SUCCESS) {
|
||||
ret = ERROR_HLS_WRITE_FAILED;
|
||||
srs_error("write ts file header failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
static int write_frame(SrsFileWriter* writer, SrsMpegtsFrame* frame, SrsSimpleBuffer* buffer)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if (!buffer->bytes() || buffer->length() <= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
char* last = buffer->bytes() + buffer->length();
|
||||
char* pos = buffer->bytes();
|
||||
|
||||
bool first = true;
|
||||
while (pos < last) {
|
||||
static char packet[188];
|
||||
char* p = packet;
|
||||
|
||||
frame->cc++;
|
||||
|
||||
// sync_byte; //8bits
|
||||
*p++ = 0x47;
|
||||
// pid; //13bits
|
||||
*p++ = (frame->pid >> 8) & 0x1f;
|
||||
// payload_unit_start_indicator; //1bit
|
||||
if (first) {
|
||||
p[-1] |= 0x40;
|
||||
}
|
||||
*p++ = frame->pid;
|
||||
|
||||
// transport_scrambling_control; //2bits
|
||||
// adaption_field_control; //2bits, 0x01: PayloadOnly
|
||||
// continuity_counter; //4bits
|
||||
*p++ = 0x10 | (frame->cc & 0x0f);
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
if (frame->key) {
|
||||
p[-1] |= 0x20; // Both Adaption and Payload
|
||||
*p++ = 7; // size
|
||||
*p++ = 0x50; // random access + PCR
|
||||
p = write_pcr(p, frame->dts - SRS_AUTO_HLS_DELAY);
|
||||
}
|
||||
|
||||
// PES header
|
||||
// packet_start_code_prefix; //24bits, '00 00 01'
|
||||
*p++ = 0x00;
|
||||
*p++ = 0x00;
|
||||
*p++ = 0x01;
|
||||
//8bits
|
||||
*p++ = frame->sid;
|
||||
|
||||
// pts(33bits) need 5bytes.
|
||||
u_int8_t header_size = 5;
|
||||
u_int8_t flags = 0x80; // pts
|
||||
|
||||
// dts(33bits) need 5bytes also
|
||||
if (frame->dts != frame->pts) {
|
||||
header_size += 5;
|
||||
flags |= 0x40; // dts
|
||||
}
|
||||
|
||||
// 3bytes: flag fields from PES_packet_length to PES_header_data_length
|
||||
int pes_size = (last - pos) + header_size + 3;
|
||||
if (pes_size > 0xffff) {
|
||||
/**
|
||||
* when actual packet length > 0xffff(65535),
|
||||
* which exceed the max u_int16_t packet length,
|
||||
* use 0 packet length, the next unit start indicates the end of packet.
|
||||
*/
|
||||
pes_size = 0;
|
||||
}
|
||||
|
||||
// PES_packet_length; //16bits
|
||||
*p++ = (pes_size >> 8);
|
||||
*p++ = pes_size;
|
||||
|
||||
// PES_scrambling_control; //2bits, '10'
|
||||
// PES_priority; //1bit
|
||||
// data_alignment_indicator; //1bit
|
||||
// copyright; //1bit
|
||||
// original_or_copy; //1bit
|
||||
*p++ = 0x80; /* H222 */
|
||||
|
||||
// PTS_DTS_flags; //2bits
|
||||
// ESCR_flag; //1bit
|
||||
// ES_rate_flag; //1bit
|
||||
// DSM_trick_mode_flag; //1bit
|
||||
// additional_copy_info_flag; //1bit
|
||||
// PES_CRC_flag; //1bit
|
||||
// PES_extension_flag; //1bit
|
||||
*p++ = flags;
|
||||
|
||||
// PES_header_data_length; //8bits
|
||||
*p++ = header_size;
|
||||
|
||||
// pts; // 33bits
|
||||
p = write_pts(p, flags >> 6, frame->pts + SRS_AUTO_HLS_DELAY);
|
||||
|
||||
// dts; // 33bits
|
||||
if (frame->dts != frame->pts) {
|
||||
p = write_pts(p, 1, frame->dts + SRS_AUTO_HLS_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
int body_size = sizeof(packet) - (p - packet);
|
||||
int in_size = last - pos;
|
||||
|
||||
if (body_size <= in_size) {
|
||||
memcpy(p, pos, body_size);
|
||||
pos += body_size;
|
||||
} else {
|
||||
p = fill_stuff(p, packet, body_size, in_size);
|
||||
memcpy(p, pos, in_size);
|
||||
pos = last;
|
||||
}
|
||||
|
||||
// write ts packet
|
||||
if ((ret = writer->write(packet, sizeof(packet), NULL)) != ERROR_SUCCESS) {
|
||||
ret = ERROR_HLS_WRITE_FAILED;
|
||||
srs_error("write ts file failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
private:
|
||||
static char* fill_stuff(char* pes_body_end, char* packet, int body_size, int in_size)
|
||||
{
|
||||
char* p = pes_body_end;
|
||||
|
||||
// insert the stuff bytes before PES body
|
||||
int stuff_size = (body_size - in_size);
|
||||
|
||||
// adaption_field_control; //2bits
|
||||
if (packet[3] & 0x20) {
|
||||
// has adaptation
|
||||
// packet[4]: adaption_field_length
|
||||
// packet[5]: adaption field data
|
||||
// base: start of PES body
|
||||
char* base = &packet[5] + packet[4];
|
||||
int len = p - base;
|
||||
p = (char*)memmove(base + stuff_size, base, len) + len;
|
||||
// increase the adaption field size.
|
||||
packet[4] += stuff_size;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// create adaption field.
|
||||
// adaption_field_control; //2bits
|
||||
packet[3] |= 0x20;
|
||||
// base: start of PES body
|
||||
char* base = &packet[4];
|
||||
int len = p - base;
|
||||
p = (char*)memmove(base + stuff_size, base, len) + len;
|
||||
// adaption_field_length; //8bits
|
||||
packet[4] = (stuff_size - 1);
|
||||
if (stuff_size >= 2) {
|
||||
// adaption field flags.
|
||||
packet[5] = 0;
|
||||
// adaption data.
|
||||
if (stuff_size > 2) {
|
||||
memset(&packet[6], 0xff, stuff_size - 2);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
static char* write_pcr(char* p, int64_t pcr)
|
||||
{
|
||||
// the pcr=dts-delay
|
||||
// and the pcr maybe negative
|
||||
// @see https://github.com/winlinvip/simple-rtmp-server/issues/268
|
||||
int64_t v = srs_max(0, pcr);
|
||||
|
||||
*p++ = (char) (v >> 25);
|
||||
*p++ = (char) (v >> 17);
|
||||
*p++ = (char) (v >> 9);
|
||||
*p++ = (char) (v >> 1);
|
||||
*p++ = (char) (v << 7 | 0x7e);
|
||||
*p++ = 0;
|
||||
|
||||
return p;
|
||||
}
|
||||
static char* write_pts(char* p, u_int8_t fb, int64_t pts)
|
||||
{
|
||||
int32_t val;
|
||||
|
||||
val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
|
||||
*p++ = val;
|
||||
|
||||
val = (((pts >> 15) & 0x7fff) << 1) | 1;
|
||||
*p++ = (val >> 8);
|
||||
*p++ = val;
|
||||
|
||||
val = (((pts) & 0x7fff) << 1) | 1;
|
||||
*p++ = (val >> 8);
|
||||
*p++ = val;
|
||||
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
SrsTSMuxer::SrsTSMuxer()
|
||||
{
|
||||
writer = new SrsFileWriter();
|
||||
}
|
||||
|
||||
SrsTSMuxer::~SrsTSMuxer()
|
||||
{
|
||||
close();
|
||||
srs_freep(writer);
|
||||
}
|
||||
|
||||
int SrsTSMuxer::open(string _path)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
path = _path;
|
||||
|
||||
close();
|
||||
|
||||
if ((ret = writer->open(path)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// write mpegts header
|
||||
if ((ret = SrsMpegtsWriter::write_header(writer)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsTSMuxer::write_audio(SrsMpegtsFrame* af, SrsSimpleBuffer* ab)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if ((ret = SrsMpegtsWriter::write_frame(writer, af, ab)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsTSMuxer::write_video(SrsMpegtsFrame* vf, SrsSimpleBuffer* vb)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if ((ret = SrsMpegtsWriter::write_frame(writer, vf, vb)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SrsTSMuxer::close()
|
||||
{
|
||||
writer->close();
|
||||
}
|
||||
|
||||
SrsHlsSegment::SrsHlsSegment()
|
||||
{
|
||||
duration = 0;
|
||||
sequence_no = 0;
|
||||
muxer = new SrsTSMuxer();
|
||||
segment_start_dts = 0;
|
||||
is_sequence_header = false;
|
||||
writer = new SrsFileWriter();
|
||||
muxer = new SrsTSMuxer(writer);
|
||||
}
|
||||
|
||||
SrsHlsSegment::~SrsHlsSegment()
|
||||
{
|
||||
srs_freep(muxer);
|
||||
srs_freep(writer);
|
||||
}
|
||||
|
||||
void SrsHlsSegment::update_duration(int64_t current_frame_dts)
|
||||
|
@ -926,6 +580,7 @@ int SrsHlsCache::write_audio(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t
|
|||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: config it.
|
||||
// in ms, audio delay to flush the audios.
|
||||
int64_t audio_delay = SRS_CONF_DEFAULT_AAC_DELAY;
|
||||
|
|
|
@ -52,25 +52,6 @@ class SrsSimpleBuffer;
|
|||
class SrsTsAacJitter;
|
||||
class SrsTsCache;
|
||||
|
||||
/**
|
||||
* write data from frame(header info) and buffer(data) to ts file.
|
||||
* it's a simple object wrapper for utility from nginx-rtmp: SrsMpegtsWriter
|
||||
*/
|
||||
class SrsTSMuxer
|
||||
{
|
||||
private:
|
||||
SrsFileWriter* writer;
|
||||
std::string path;
|
||||
public:
|
||||
SrsTSMuxer();
|
||||
virtual ~SrsTSMuxer();
|
||||
public:
|
||||
virtual int open(std::string _path);
|
||||
virtual int write_audio(SrsMpegtsFrame* af, SrsSimpleBuffer* ab);
|
||||
virtual int write_video(SrsMpegtsFrame* vf, SrsSimpleBuffer* vb);
|
||||
virtual void close();
|
||||
};
|
||||
|
||||
/**
|
||||
* the wrapper of m3u8 segment from specification:
|
||||
*
|
||||
|
@ -89,6 +70,7 @@ public:
|
|||
// ts full file to write.
|
||||
std::string full_path;
|
||||
// the muxer to write ts.
|
||||
SrsFileWriter* writer;
|
||||
SrsTSMuxer* muxer;
|
||||
// current segment start dts for m3u8
|
||||
int64_t segment_start_dts;
|
||||
|
|
|
@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
// current release version
|
||||
#define VERSION_MAJOR 2
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_REVISION 101
|
||||
#define VERSION_REVISION 102
|
||||
|
||||
// server info.
|
||||
#define RTMP_SIG_SRS_KEY "SRS"
|
||||
|
|
|
@ -28,6 +28,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#include <srs_kernel_stream.hpp>
|
||||
#include <srs_kernel_utility.hpp>
|
||||
#include <srs_kernel_buffer.hpp>
|
||||
#include <srs_kernel_file.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// in ms, for HLS aac sync time.
|
||||
#define SRS_CONF_DEFAULT_AAC_SYNC 100
|
||||
|
@ -65,6 +68,294 @@ int aac_sample_rates[] =
|
|||
7350, 0, 0, 0
|
||||
};
|
||||
|
||||
// @see: NGX_RTMP_HLS_DELAY,
|
||||
// 63000: 700ms, ts_tbn=90000
|
||||
#define SRS_AUTO_HLS_DELAY 63000
|
||||
|
||||
// @see: ngx_rtmp_mpegts_header
|
||||
u_int8_t mpegts_header[] = {
|
||||
/* TS */
|
||||
0x47, 0x40, 0x00, 0x10, 0x00,
|
||||
/* PSI */
|
||||
0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
||||
/* PAT */
|
||||
0x00, 0x01, 0xf0, 0x01,
|
||||
/* CRC */
|
||||
0x2e, 0x70, 0x19, 0x05,
|
||||
/* stuffing 167 bytes */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
|
||||
/* TS */
|
||||
0x47, 0x50, 0x01, 0x10, 0x00,
|
||||
/* PSI */
|
||||
0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
||||
/* PMT */
|
||||
0xe1, 0x00,
|
||||
0xf0, 0x00,
|
||||
// must generate header with/without video, @see:
|
||||
// https://github.com/winlinvip/simple-rtmp-server/issues/40
|
||||
0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264, pid=0x100=256 */
|
||||
0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac, pid=0x101=257 */
|
||||
/*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */
|
||||
/* CRC */
|
||||
0x2f, 0x44, 0xb9, 0x9b, /* crc for aac */
|
||||
/*0x4e, 0x59, 0x3d, 0x1e,*/ /* crc for mp3 */
|
||||
/* stuffing 157 bytes */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
// @see: ngx_rtmp_mpegts.c
|
||||
// TODO: support full mpegts feature in future.
|
||||
class SrsMpegtsWriter
|
||||
{
|
||||
public:
|
||||
static int write_header(SrsFileWriter* writer)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if ((ret = writer->write(mpegts_header, sizeof(mpegts_header), NULL)) != ERROR_SUCCESS) {
|
||||
ret = ERROR_HLS_WRITE_FAILED;
|
||||
srs_error("write ts file header failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
static int write_frame(SrsFileWriter* writer, SrsMpegtsFrame* frame, SrsSimpleBuffer* buffer)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if (!buffer->bytes() || buffer->length() <= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
char* last = buffer->bytes() + buffer->length();
|
||||
char* pos = buffer->bytes();
|
||||
|
||||
bool first = true;
|
||||
while (pos < last) {
|
||||
static char packet[188];
|
||||
char* p = packet;
|
||||
|
||||
frame->cc++;
|
||||
|
||||
// sync_byte; //8bits
|
||||
*p++ = 0x47;
|
||||
// pid; //13bits
|
||||
*p++ = (frame->pid >> 8) & 0x1f;
|
||||
// payload_unit_start_indicator; //1bit
|
||||
if (first) {
|
||||
p[-1] |= 0x40;
|
||||
}
|
||||
*p++ = frame->pid;
|
||||
|
||||
// transport_scrambling_control; //2bits
|
||||
// adaption_field_control; //2bits, 0x01: PayloadOnly
|
||||
// continuity_counter; //4bits
|
||||
*p++ = 0x10 | (frame->cc & 0x0f);
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
if (frame->key) {
|
||||
p[-1] |= 0x20; // Both Adaption and Payload
|
||||
*p++ = 7; // size
|
||||
*p++ = 0x50; // random access + PCR
|
||||
p = write_pcr(p, frame->dts - SRS_AUTO_HLS_DELAY);
|
||||
}
|
||||
|
||||
// PES header
|
||||
// packet_start_code_prefix; //24bits, '00 00 01'
|
||||
*p++ = 0x00;
|
||||
*p++ = 0x00;
|
||||
*p++ = 0x01;
|
||||
//8bits
|
||||
*p++ = frame->sid;
|
||||
|
||||
// pts(33bits) need 5bytes.
|
||||
u_int8_t header_size = 5;
|
||||
u_int8_t flags = 0x80; // pts
|
||||
|
||||
// dts(33bits) need 5bytes also
|
||||
if (frame->dts != frame->pts) {
|
||||
header_size += 5;
|
||||
flags |= 0x40; // dts
|
||||
}
|
||||
|
||||
// 3bytes: flag fields from PES_packet_length to PES_header_data_length
|
||||
int pes_size = (last - pos) + header_size + 3;
|
||||
if (pes_size > 0xffff) {
|
||||
/**
|
||||
* when actual packet length > 0xffff(65535),
|
||||
* which exceed the max u_int16_t packet length,
|
||||
* use 0 packet length, the next unit start indicates the end of packet.
|
||||
*/
|
||||
pes_size = 0;
|
||||
}
|
||||
|
||||
// PES_packet_length; //16bits
|
||||
*p++ = (pes_size >> 8);
|
||||
*p++ = pes_size;
|
||||
|
||||
// PES_scrambling_control; //2bits, '10'
|
||||
// PES_priority; //1bit
|
||||
// data_alignment_indicator; //1bit
|
||||
// copyright; //1bit
|
||||
// original_or_copy; //1bit
|
||||
*p++ = 0x80; /* H222 */
|
||||
|
||||
// PTS_DTS_flags; //2bits
|
||||
// ESCR_flag; //1bit
|
||||
// ES_rate_flag; //1bit
|
||||
// DSM_trick_mode_flag; //1bit
|
||||
// additional_copy_info_flag; //1bit
|
||||
// PES_CRC_flag; //1bit
|
||||
// PES_extension_flag; //1bit
|
||||
*p++ = flags;
|
||||
|
||||
// PES_header_data_length; //8bits
|
||||
*p++ = header_size;
|
||||
|
||||
// pts; // 33bits
|
||||
p = write_pts(p, flags >> 6, frame->pts + SRS_AUTO_HLS_DELAY);
|
||||
|
||||
// dts; // 33bits
|
||||
if (frame->dts != frame->pts) {
|
||||
p = write_pts(p, 1, frame->dts + SRS_AUTO_HLS_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
int body_size = sizeof(packet) - (p - packet);
|
||||
int in_size = last - pos;
|
||||
|
||||
if (body_size <= in_size) {
|
||||
memcpy(p, pos, body_size);
|
||||
pos += body_size;
|
||||
} else {
|
||||
p = fill_stuff(p, packet, body_size, in_size);
|
||||
memcpy(p, pos, in_size);
|
||||
pos = last;
|
||||
}
|
||||
|
||||
// write ts packet
|
||||
if ((ret = writer->write(packet, sizeof(packet), NULL)) != ERROR_SUCCESS) {
|
||||
if (!srs_is_client_gracefully_close(ret)) {
|
||||
srs_error("write ts file failed. ret=%d", ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
private:
|
||||
static char* fill_stuff(char* pes_body_end, char* packet, int body_size, int in_size)
|
||||
{
|
||||
char* p = pes_body_end;
|
||||
|
||||
// insert the stuff bytes before PES body
|
||||
int stuff_size = (body_size - in_size);
|
||||
|
||||
// adaption_field_control; //2bits
|
||||
if (packet[3] & 0x20) {
|
||||
// has adaptation
|
||||
// packet[4]: adaption_field_length
|
||||
// packet[5]: adaption field data
|
||||
// base: start of PES body
|
||||
char* base = &packet[5] + packet[4];
|
||||
int len = p - base;
|
||||
p = (char*)memmove(base + stuff_size, base, len) + len;
|
||||
// increase the adaption field size.
|
||||
packet[4] += stuff_size;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// create adaption field.
|
||||
// adaption_field_control; //2bits
|
||||
packet[3] |= 0x20;
|
||||
// base: start of PES body
|
||||
char* base = &packet[4];
|
||||
int len = p - base;
|
||||
p = (char*)memmove(base + stuff_size, base, len) + len;
|
||||
// adaption_field_length; //8bits
|
||||
packet[4] = (stuff_size - 1);
|
||||
if (stuff_size >= 2) {
|
||||
// adaption field flags.
|
||||
packet[5] = 0;
|
||||
// adaption data.
|
||||
if (stuff_size > 2) {
|
||||
memset(&packet[6], 0xff, stuff_size - 2);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
static char* write_pcr(char* p, int64_t pcr)
|
||||
{
|
||||
// the pcr=dts-delay
|
||||
// and the pcr maybe negative
|
||||
// @see https://github.com/winlinvip/simple-rtmp-server/issues/268
|
||||
int64_t v = srs_max(0, pcr);
|
||||
|
||||
*p++ = (char) (v >> 25);
|
||||
*p++ = (char) (v >> 17);
|
||||
*p++ = (char) (v >> 9);
|
||||
*p++ = (char) (v >> 1);
|
||||
*p++ = (char) (v << 7 | 0x7e);
|
||||
*p++ = 0;
|
||||
|
||||
return p;
|
||||
}
|
||||
static char* write_pts(char* p, u_int8_t fb, int64_t pts)
|
||||
{
|
||||
int32_t val;
|
||||
|
||||
val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
|
||||
*p++ = val;
|
||||
|
||||
val = (((pts >> 15) & 0x7fff) << 1) | 1;
|
||||
*p++ = (val >> 8);
|
||||
*p++ = val;
|
||||
|
||||
val = (((pts) & 0x7fff) << 1) | 1;
|
||||
*p++ = (val >> 8);
|
||||
*p++ = val;
|
||||
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
SrsMpegtsFrame::SrsMpegtsFrame()
|
||||
{
|
||||
pts = dts = 0;
|
||||
|
@ -72,6 +363,63 @@ SrsMpegtsFrame::SrsMpegtsFrame()
|
|||
key = false;
|
||||
}
|
||||
|
||||
SrsTSMuxer::SrsTSMuxer(SrsFileWriter* w)
|
||||
{
|
||||
writer = w;
|
||||
}
|
||||
|
||||
SrsTSMuxer::~SrsTSMuxer()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
int SrsTSMuxer::open(string _path)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
path = _path;
|
||||
|
||||
close();
|
||||
|
||||
if ((ret = writer->open(path)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// write mpegts header
|
||||
if ((ret = SrsMpegtsWriter::write_header(writer)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsTSMuxer::write_audio(SrsMpegtsFrame* af, SrsSimpleBuffer* ab)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if ((ret = SrsMpegtsWriter::write_frame(writer, af, ab)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsTSMuxer::write_video(SrsMpegtsFrame* vf, SrsSimpleBuffer* vb)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if ((ret = SrsMpegtsWriter::write_frame(writer, vf, vb)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SrsTSMuxer::close()
|
||||
{
|
||||
writer->close();
|
||||
}
|
||||
|
||||
SrsTsAacJitter::SrsTsAacJitter()
|
||||
{
|
||||
base_pts = 0;
|
||||
|
|
|
@ -30,6 +30,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
|
||||
#include <srs_core.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <srs_kernel_codec.hpp>
|
||||
|
||||
class SrsStream;
|
||||
|
@ -37,6 +39,7 @@ class SrsMpegtsFrame;
|
|||
class SrsSimpleBuffer;
|
||||
class SrsAvcAacCodec;
|
||||
class SrsCodecSample;
|
||||
class SrsFileWriter;
|
||||
|
||||
/**
|
||||
* the public data, event HLS disable, others can use it.
|
||||
|
@ -57,6 +60,9 @@ extern int aac_sample_rates[];
|
|||
// in ms, for HLS aac flush the audio
|
||||
#define SRS_CONF_DEFAULT_AAC_DELAY 100
|
||||
|
||||
// max PES packets size to flush the video.
|
||||
#define SRS_AUTO_HLS_AUDIO_CACHE_SIZE 1024 * 1024
|
||||
|
||||
/**
|
||||
* the FLV/RTMP supported audio sample size.
|
||||
* Size of each audio sample. This parameter only pertains to
|
||||
|
@ -103,6 +109,25 @@ public:
|
|||
SrsMpegtsFrame();
|
||||
};
|
||||
|
||||
/**
|
||||
* write data from frame(header info) and buffer(data) to ts file.
|
||||
* it's a simple object wrapper for utility from nginx-rtmp: SrsMpegtsWriter
|
||||
*/
|
||||
class SrsTSMuxer
|
||||
{
|
||||
private:
|
||||
SrsFileWriter* writer;
|
||||
std::string path;
|
||||
public:
|
||||
SrsTSMuxer(SrsFileWriter* w);
|
||||
virtual ~SrsTSMuxer();
|
||||
public:
|
||||
virtual int open(std::string _path);
|
||||
virtual int write_audio(SrsMpegtsFrame* af, SrsSimpleBuffer* ab);
|
||||
virtual int write_video(SrsMpegtsFrame* vf, SrsSimpleBuffer* vb);
|
||||
virtual void close();
|
||||
};
|
||||
|
||||
/**
|
||||
* jitter correct for audio,
|
||||
* the sample rate 44100/32000 will lost precise,
|
||||
|
|
|
@ -36,18 +36,23 @@ using namespace std;
|
|||
#include <srs_kernel_error.hpp>
|
||||
#include <srs_kernel_file.hpp>
|
||||
#include <srs_kernel_avc.hpp>
|
||||
#include <srs_kernel_buffer.hpp>
|
||||
|
||||
SrsTsEncoder::SrsTsEncoder()
|
||||
{
|
||||
_fs = NULL;
|
||||
codec = new SrsAvcAacCodec();
|
||||
sample = new SrsCodecSample();
|
||||
cache = new SrsTsCache();
|
||||
muxer = NULL;
|
||||
}
|
||||
|
||||
SrsTsEncoder::~SrsTsEncoder()
|
||||
{
|
||||
srs_freep(codec);
|
||||
srs_freep(sample);
|
||||
srs_freep(cache);
|
||||
srs_freep(muxer);
|
||||
}
|
||||
|
||||
int SrsTsEncoder::initialize(SrsFileWriter* fs)
|
||||
|
@ -63,6 +68,13 @@ int SrsTsEncoder::initialize(SrsFileWriter* fs)
|
|||
}
|
||||
|
||||
_fs = fs;
|
||||
|
||||
srs_freep(muxer);
|
||||
muxer = new SrsTSMuxer(fs);
|
||||
|
||||
if ((ret = muxer->open("")) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -91,10 +103,20 @@ int SrsTsEncoder::write_audio(int64_t timestamp, char* data, int size)
|
|||
// for the packet is filtered by consumer.
|
||||
int64_t dts = timestamp * 90;
|
||||
|
||||
/*if ((ret = hls_cache->write_audio(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
|
||||
srs_error("http: ts cache write audio failed. ret=%d", ret);
|
||||
// write audio to cache.
|
||||
if ((ret = cache->cache_audio(codec, dts, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}*/
|
||||
}
|
||||
|
||||
// flush if buffer exceed max size.
|
||||
if (cache->ab->length() > SRS_AUTO_HLS_AUDIO_CACHE_SIZE) {
|
||||
if ((ret = muxer->write_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// write success, clear and free the buffer
|
||||
cache->ab->erase(cache->ab->length());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -126,10 +148,18 @@ int SrsTsEncoder::write_video(int64_t timestamp, char* data, int size)
|
|||
}
|
||||
|
||||
int64_t dts = timestamp * 90;
|
||||
/*if ((ret = hls_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
|
||||
srs_error("http: ts cache write video failed. ret=%d", ret);
|
||||
|
||||
// write video to cache.
|
||||
if ((ret = cache->cache_video(codec, dts, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}*/
|
||||
}
|
||||
|
||||
if ((ret = muxer->write_video(cache->vf, cache->vb)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// write success, clear and free the buffer
|
||||
cache->vb->erase(cache->vb->length());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
|
||||
#include <string>
|
||||
|
||||
class SrsTsCache;
|
||||
class SrsTSMuxer;
|
||||
class SrsFileWriter;
|
||||
class SrsFileReader;
|
||||
class SrsAvcAacCodec;
|
||||
|
@ -46,6 +48,8 @@ private:
|
|||
private:
|
||||
SrsAvcAacCodec* codec;
|
||||
SrsCodecSample* sample;
|
||||
SrsTsCache* cache;
|
||||
SrsTSMuxer* muxer;
|
||||
public:
|
||||
SrsTsEncoder();
|
||||
virtual ~SrsTsEncoder();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue