mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
support HLS(m3u8)
This commit is contained in:
parent
749b7bdb2a
commit
0c72c56f74
5 changed files with 451 additions and 229 deletions
|
@ -332,7 +332,7 @@ int SrsClient::publish(SrsSource* source, bool is_fmle)
|
|||
SrsPithyPrint pithy_print(SRS_STAGE_PUBLISH_USER);
|
||||
|
||||
// notify the hls to prepare when publish start.
|
||||
if ((ret = source->on_publish(req->vhost)) != ERROR_SUCCESS) {
|
||||
if ((ret = source->on_publish(req->vhost, req->stream)) != ERROR_SUCCESS) {
|
||||
srs_error("hls on_publish failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -37,194 +37,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#include <srs_core_source.hpp>
|
||||
#include <srs_core_autofree.hpp>
|
||||
|
||||
SrsHLS::SrsHLS()
|
||||
{
|
||||
hls_enabled = false;
|
||||
codec = new SrsCodec();
|
||||
sample = new SrsCodecSample();
|
||||
muxer = NULL;
|
||||
jitter = new SrsRtmpJitter();
|
||||
}
|
||||
// @see: NGX_RTMP_HLS_DELAY,
|
||||
// 63000: 700ms, ts_tbn=90000
|
||||
#define SRS_HLS_DELAY 63000
|
||||
|
||||
SrsHLS::~SrsHLS()
|
||||
{
|
||||
srs_freep(codec);
|
||||
srs_freep(sample);
|
||||
srs_freep(muxer);
|
||||
srs_freep(jitter);
|
||||
}
|
||||
// the mpegts header specifed the video/audio pid.
|
||||
#define TS_VIDEO_PID 256
|
||||
#define TS_AUDIO_PID 257
|
||||
|
||||
int SrsHLS::on_publish(std::string _vhost)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
// TODO: check config.
|
||||
if (muxer) {
|
||||
hls_enabled = true;
|
||||
srs_trace("hls is reopen, continue streaming HLS, vhost=%s", _vhost.c_str());
|
||||
return ret;
|
||||
}
|
||||
|
||||
vhost = _vhost;
|
||||
muxer = new SrsTSMuxer();
|
||||
|
||||
// try to open the HLS muxer
|
||||
SrsConfDirective* conf = config->get_hls(vhost);
|
||||
if (!conf && conf->arg0() == "off") {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: check the audio and video, ensure both exsists.
|
||||
// for use fixed mpegts header specifeid the audio and video pid.
|
||||
|
||||
hls_enabled = true;
|
||||
|
||||
std::string path = SRS_CONF_DEFAULT_HLS_PATH;
|
||||
if ((conf = config->get_hls_path(vhost)) != NULL) {
|
||||
path = conf->arg0();
|
||||
}
|
||||
|
||||
// TODO: generate by m3u8 muxer.
|
||||
path += "/1.ts";
|
||||
|
||||
if ((ret = muxer->open(path)) != ERROR_SUCCESS) {
|
||||
srs_error("open hls muxer failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SrsHLS::on_unpublish()
|
||||
{
|
||||
hls_enabled = false;
|
||||
//muxer->close();
|
||||
//srs_freep(muxer);
|
||||
}
|
||||
|
||||
int SrsHLS::on_meta_data(SrsOnMetaDataPacket* metadata)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if (!metadata || !metadata->metadata) {
|
||||
srs_trace("no metadata persent, hls ignored it.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
SrsAmf0Object* obj = metadata->metadata;
|
||||
if (obj->size() <= 0) {
|
||||
srs_trace("no metadata persent, hls ignored it.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// finger out the codec info from metadata if possible.
|
||||
SrsAmf0Any* prop = NULL;
|
||||
|
||||
if ((prop = obj->get_property("duration")) != NULL && prop->is_number()) {
|
||||
codec->duration = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("width")) != NULL && prop->is_number()) {
|
||||
codec->width = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("height")) != NULL && prop->is_number()) {
|
||||
codec->height = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("framerate")) != NULL && prop->is_number()) {
|
||||
codec->frame_rate = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("videocodecid")) != NULL && prop->is_number()) {
|
||||
codec->video_codec_id = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("videodatarate")) != NULL && prop->is_number()) {
|
||||
codec->video_data_rate = (int)(1000 * srs_amf0_convert<SrsAmf0Number>(prop)->value);
|
||||
}
|
||||
|
||||
if ((prop = obj->get_property("audiocodecid")) != NULL && prop->is_number()) {
|
||||
codec->audio_codec_id = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("audiodatarate")) != NULL && prop->is_number()) {
|
||||
codec->audio_data_rate = (int)(1000 * srs_amf0_convert<SrsAmf0Number>(prop)->value);
|
||||
}
|
||||
|
||||
// ignore the following, for each flv/rtmp packet contains them:
|
||||
// audiosamplerate, sample->sound_rate
|
||||
// audiosamplesize, sample->sound_size
|
||||
// stereo, sample->sound_type
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHLS::on_audio(SrsSharedPtrMessage* audio)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsAutoFree(SrsSharedPtrMessage, audio, false);
|
||||
|
||||
sample->clear();
|
||||
if ((ret = codec->audio_aac_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (codec->audio_codec_id != SrsCodecAudioAAC) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: maybe donot need to demux the aac?
|
||||
if (!hls_enabled) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ignore sequence header
|
||||
if (sample->aac_packet_type == SrsCodecAudioTypeSequenceHeader) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = jitter->correct(audio, 0, 0)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = muxer->write_audio(audio->header.timestamp, codec, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHLS::on_video(SrsSharedPtrMessage* video)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsAutoFree(SrsSharedPtrMessage, video, false);
|
||||
|
||||
sample->clear();
|
||||
if ((ret = codec->video_avc_demux(video->payload, video->size, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (codec->video_codec_id != SrsCodecVideoAVC) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: maybe donot need to demux the avc?
|
||||
if (!hls_enabled) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ignore sequence header
|
||||
if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame && sample->avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = jitter->correct(video, 0, 0)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = muxer->write_video(video->header.timestamp, codec, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
// ts aac stream id.
|
||||
#define TS_AUDIO_AAC 0xc0
|
||||
// ts avc stream id.
|
||||
#define TS_VIDEO_AVC 0xe0
|
||||
|
||||
// @see: ngx_rtmp_mpegts_header
|
||||
u_int8_t mpegts_header[] = {
|
||||
|
@ -287,10 +111,6 @@ u_int8_t mpegts_header[] = {
|
|||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
// @see: NGX_RTMP_HLS_DELAY,
|
||||
// 63000: 700ms, ts_tbn=90000
|
||||
#define SRS_HLS_DELAY 63000
|
||||
|
||||
// @see: ngx_rtmp_SrsMpegtsFrame_t
|
||||
struct SrsMpegtsFrame
|
||||
{
|
||||
|
@ -527,18 +347,29 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
// the mpegts header specifed the video/audio pid.
|
||||
#define TS_VIDEO_PID 256
|
||||
#define TS_AUDIO_PID 257
|
||||
|
||||
// ts aac stream id.
|
||||
#define TS_AUDIO_AAC 0xc0
|
||||
// ts avc stream id.
|
||||
#define TS_VIDEO_AVC 0xe0
|
||||
|
||||
SrsTSMuxer::SrsTSMuxer()
|
||||
SrsM3u8Segment::SrsM3u8Segment()
|
||||
{
|
||||
fd = -1;
|
||||
duration = 0;
|
||||
sequence_no = 0;
|
||||
muxer = new SrsTSMuxer();
|
||||
segment_start_dts = 0;
|
||||
}
|
||||
|
||||
SrsM3u8Segment::~SrsM3u8Segment()
|
||||
{
|
||||
muxer->close();
|
||||
srs_freep(muxer);
|
||||
}
|
||||
|
||||
SrsHLS::SrsHLS()
|
||||
{
|
||||
hls_enabled = false;
|
||||
codec = new SrsCodec();
|
||||
sample = new SrsCodecSample();
|
||||
current = NULL;
|
||||
jitter = new SrsRtmpJitter();
|
||||
file_index = 0;
|
||||
m3u8_dts = stream_dts = 0;
|
||||
|
||||
audio_buffer = new SrsCodecBuffer();
|
||||
video_buffer = new SrsCodecBuffer();
|
||||
|
@ -547,9 +378,20 @@ SrsTSMuxer::SrsTSMuxer()
|
|||
video_frame = new SrsMpegtsFrame();
|
||||
}
|
||||
|
||||
SrsTSMuxer::~SrsTSMuxer()
|
||||
SrsHLS::~SrsHLS()
|
||||
{
|
||||
close();
|
||||
srs_freep(codec);
|
||||
srs_freep(sample);
|
||||
srs_freep(jitter);
|
||||
|
||||
std::vector<SrsM3u8Segment*>::iterator it;
|
||||
for (it = segments.begin(); it != segments.end(); ++it) {
|
||||
SrsM3u8Segment* segment = *it;
|
||||
srs_freep(segment);
|
||||
}
|
||||
segments.clear();
|
||||
|
||||
srs_freep(current);
|
||||
|
||||
audio_buffer->free();
|
||||
video_buffer->free();
|
||||
|
@ -561,6 +403,346 @@ SrsTSMuxer::~SrsTSMuxer()
|
|||
srs_freep(video_frame);
|
||||
}
|
||||
|
||||
int SrsHLS::on_publish(std::string _vhost, std::string _stream)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
vhost = _vhost;
|
||||
stream = _stream;
|
||||
|
||||
if ((ret = reopen()) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SrsHLS::on_unpublish()
|
||||
{
|
||||
hls_enabled = false;
|
||||
}
|
||||
|
||||
int SrsHLS::on_meta_data(SrsOnMetaDataPacket* metadata)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if (!metadata || !metadata->metadata) {
|
||||
srs_trace("no metadata persent, hls ignored it.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
SrsAmf0Object* obj = metadata->metadata;
|
||||
if (obj->size() <= 0) {
|
||||
srs_trace("no metadata persent, hls ignored it.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// finger out the codec info from metadata if possible.
|
||||
SrsAmf0Any* prop = NULL;
|
||||
|
||||
if ((prop = obj->get_property("duration")) != NULL && prop->is_number()) {
|
||||
codec->duration = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("width")) != NULL && prop->is_number()) {
|
||||
codec->width = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("height")) != NULL && prop->is_number()) {
|
||||
codec->height = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("framerate")) != NULL && prop->is_number()) {
|
||||
codec->frame_rate = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("videocodecid")) != NULL && prop->is_number()) {
|
||||
codec->video_codec_id = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("videodatarate")) != NULL && prop->is_number()) {
|
||||
codec->video_data_rate = (int)(1000 * srs_amf0_convert<SrsAmf0Number>(prop)->value);
|
||||
}
|
||||
|
||||
if ((prop = obj->get_property("audiocodecid")) != NULL && prop->is_number()) {
|
||||
codec->audio_codec_id = (int)srs_amf0_convert<SrsAmf0Number>(prop)->value;
|
||||
}
|
||||
if ((prop = obj->get_property("audiodatarate")) != NULL && prop->is_number()) {
|
||||
codec->audio_data_rate = (int)(1000 * srs_amf0_convert<SrsAmf0Number>(prop)->value);
|
||||
}
|
||||
|
||||
// ignore the following, for each flv/rtmp packet contains them:
|
||||
// audiosamplerate, sample->sound_rate
|
||||
// audiosamplesize, sample->sound_size
|
||||
// stereo, sample->sound_type
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHLS::on_audio(SrsSharedPtrMessage* audio)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsAutoFree(SrsSharedPtrMessage, audio, false);
|
||||
|
||||
// TODO: maybe donot need to demux the aac?
|
||||
if (!hls_enabled) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sample->clear();
|
||||
if ((ret = codec->audio_aac_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (codec->audio_codec_id != SrsCodecAudioAAC) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ignore sequence header
|
||||
if (sample->aac_packet_type == SrsCodecAudioTypeSequenceHeader) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = jitter->correct(audio, 0, 0)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
srs_assert(current);
|
||||
|
||||
stream_dts = audio_frame->dts = audio_frame->pts = audio->header.timestamp * 90;
|
||||
audio_frame->pid = TS_AUDIO_PID;
|
||||
audio_frame->sid = TS_AUDIO_AAC;
|
||||
|
||||
if ((ret = current->muxer->write_audio(audio_frame, audio_buffer, codec, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHLS::on_video(SrsSharedPtrMessage* video)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsAutoFree(SrsSharedPtrMessage, video, false);
|
||||
|
||||
// TODO: maybe donot need to demux the avc?
|
||||
if (!hls_enabled) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sample->clear();
|
||||
if ((ret = codec->video_avc_demux(video->payload, video->size, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (codec->video_codec_id != SrsCodecVideoAVC) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ignore sequence header
|
||||
if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame && sample->avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = jitter->correct(video, 0, 0)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
stream_dts = video_frame->dts = video->header.timestamp * 90;
|
||||
video_frame->pts = video_frame->dts + sample->cts * 90;
|
||||
video_frame->pid = TS_VIDEO_PID;
|
||||
video_frame->sid = TS_VIDEO_AVC;
|
||||
video_frame->key = sample->frame_type == SrsCodecVideoAVCFrameKeyFrame;
|
||||
|
||||
// reopen the muxer for a gop
|
||||
if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame) {
|
||||
int64_t diff = stream_dts - m3u8_dts;
|
||||
// 10s.
|
||||
if (diff / 90000 >= 10) {
|
||||
if ((ret = reopen()) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
srs_assert(current);
|
||||
if ((ret = current->muxer->write_video(video_frame, video_buffer, codec, sample)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHLS::reopen()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
// try to open the HLS muxer
|
||||
SrsConfDirective* conf = config->get_hls(vhost);
|
||||
if (!conf && conf->arg0() == "off") {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: check the audio and video, ensure both exsists.
|
||||
// for use fixed mpegts header specifeid the audio and video pid.
|
||||
|
||||
hls_enabled = true;
|
||||
|
||||
hls_path = SRS_CONF_DEFAULT_HLS_PATH;
|
||||
if ((conf = config->get_hls_path(vhost)) != NULL) {
|
||||
hls_path = conf->arg0();
|
||||
}
|
||||
|
||||
// start new segment.
|
||||
if (current) {
|
||||
current->duration = (stream_dts - current->segment_start_dts) / 90000.0;
|
||||
segments.push_back(current);
|
||||
current = NULL;
|
||||
|
||||
if ((ret = refresh_m3u8()) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
// new segment.
|
||||
current = new SrsM3u8Segment();
|
||||
m3u8_dts = current->segment_start_dts = stream_dts;
|
||||
|
||||
// generate filename.
|
||||
char filename[128];
|
||||
snprintf(filename, sizeof(filename), "%s-%d.ts", stream.c_str(), file_index++);
|
||||
|
||||
current->full_path = hls_path;
|
||||
current->full_path += "/";
|
||||
current->full_path += filename;
|
||||
|
||||
// TODO: support base url, and so on.
|
||||
current->uri = filename;
|
||||
|
||||
if ((ret = current->muxer->open(current->full_path)) != ERROR_SUCCESS) {
|
||||
srs_error("open hls muxer failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_trace("open HLS muxer success. vhost=%s, path=%s", vhost.c_str(), current->full_path.c_str());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHLS::refresh_m3u8()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
int fd = -1;
|
||||
ret = _refresh_m3u8(fd);
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHLS::_refresh_m3u8(int& fd)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
// no segments, return.
|
||||
if (segments.size() == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
m3u8 = hls_path;
|
||||
m3u8 += "/";
|
||||
m3u8 += stream;
|
||||
m3u8 += ".m3u8";
|
||||
|
||||
int flags = O_CREAT|O_WRONLY|O_TRUNC;
|
||||
mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH;
|
||||
if ((fd = ::open(m3u8.c_str(), flags, mode)) < 0) {
|
||||
ret = ERROR_HLS_OPEN_FAILED;
|
||||
srs_error("open m3u8 file %s failed. ret=%d", m3u8.c_str(), ret);
|
||||
return ret;
|
||||
}
|
||||
srs_info("open m3u8 file %s success.", m3u8.c_str());
|
||||
|
||||
// #EXTM3U\n#EXT-X-VERSION:3\n
|
||||
char header[] = {
|
||||
// #EXTM3U\n
|
||||
0x23, 0x45, 0x58, 0x54, 0x4d, 0x33, 0x55, 0xa,
|
||||
// #EXT-X-VERSION:3\n
|
||||
0x23, 0x45, 0x58, 0x54, 0x2d, 0x58, 0x2d, 0x56, 0x45, 0x52,
|
||||
0x53, 0x49, 0x4f, 0x4e, 0x3a, 0x33, 0xa
|
||||
};
|
||||
if (::write(fd, header, sizeof(header)) != sizeof(header)) {
|
||||
ret = ERROR_HLS_WRITE_FAILED;
|
||||
srs_error("write m3u8 header failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("write m3u8 header success.");
|
||||
|
||||
// #EXT-X-MEDIA-SEQUENCE:4294967295\n
|
||||
SrsM3u8Segment* first = *segments.begin();
|
||||
char sequence[34] = {};
|
||||
int len = snprintf(sequence, sizeof(sequence), "#EXT-X-MEDIA-SEQUENCE:%d\n", first->sequence_no);
|
||||
if (::write(fd, sequence, len) != len) {
|
||||
ret = ERROR_HLS_WRITE_FAILED;
|
||||
srs_error("write m3u8 sequence failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("write m3u8 sequence success.");
|
||||
|
||||
// #EXT-X-TARGETDURATION:4294967295\n
|
||||
int target_duration = 0;
|
||||
std::vector<SrsM3u8Segment*>::iterator it;
|
||||
for (it = segments.begin(); it != segments.end(); ++it) {
|
||||
SrsM3u8Segment* segment = *it;
|
||||
target_duration = srs_max(target_duration, (int)segment->duration);
|
||||
}
|
||||
// TODO: maybe need to take an around value
|
||||
target_duration += 1;
|
||||
char duration[34];
|
||||
len = snprintf(duration, sizeof(duration), "#EXT-X-TARGETDURATION:%d\n", target_duration);
|
||||
if (::write(fd, duration, len) != len) {
|
||||
ret = ERROR_HLS_WRITE_FAILED;
|
||||
srs_error("write m3u8 duration failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("write m3u8 duration success.");
|
||||
|
||||
// write all segments
|
||||
for (it = segments.begin(); it != segments.end(); ++it) {
|
||||
SrsM3u8Segment* segment = *it;
|
||||
|
||||
// "#EXTINF:4294967295.208,\n"
|
||||
char ext_info[25];
|
||||
len = snprintf(ext_info, sizeof(ext_info), "#EXTINF:%.3f\n", segment->duration);
|
||||
if (::write(fd, ext_info, len) != len) {
|
||||
ret = ERROR_HLS_WRITE_FAILED;
|
||||
srs_error("write m3u8 segment failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("write m3u8 segment success.");
|
||||
|
||||
// file name
|
||||
std::string filename = segment->uri;
|
||||
filename += "\n";
|
||||
if (::write(fd, filename.c_str(), filename.length()) != (int)filename.length()) {
|
||||
ret = ERROR_HLS_WRITE_FAILED;
|
||||
srs_error("write m3u8 segment uri failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("write m3u8 segment uri success.");
|
||||
}
|
||||
srs_info("write m3u8 %s success.", m3u8.c_str());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SrsTSMuxer::SrsTSMuxer()
|
||||
{
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
SrsTSMuxer::~SrsTSMuxer()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
int SrsTSMuxer::open(std::string _path)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
@ -585,14 +767,10 @@ int SrsTSMuxer::open(std::string _path)
|
|||
return ret;
|
||||
}
|
||||
|
||||
int SrsTSMuxer::write_audio(u_int32_t time, SrsCodec* codec, SrsCodecSample* sample)
|
||||
int SrsTSMuxer::write_audio(SrsMpegtsFrame* audio_frame, SrsCodecBuffer* audio_buffer, SrsCodec* codec, SrsCodecSample* sample)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
audio_frame->dts = audio_frame->pts = time * 90;
|
||||
audio_frame->pid = TS_AUDIO_PID;
|
||||
audio_frame->sid = TS_AUDIO_AAC;
|
||||
|
||||
for (int i = 0; i < sample->nb_buffers; i++) {
|
||||
SrsCodecBuffer* buf = &sample->buffers[i];
|
||||
int32_t size = buf->size;
|
||||
|
@ -660,16 +838,10 @@ int SrsTSMuxer::write_audio(u_int32_t time, SrsCodec* codec, SrsCodecSample* sam
|
|||
return ret;
|
||||
}
|
||||
|
||||
int SrsTSMuxer::write_video(u_int32_t time, SrsCodec* codec, SrsCodecSample* sample)
|
||||
int SrsTSMuxer::write_video(SrsMpegtsFrame* video_frame, SrsCodecBuffer* video_buffer, SrsCodec* codec, SrsCodecSample* sample)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
video_frame->dts = time * 90;
|
||||
video_frame->pts = video_frame->dts + sample->cts * 90;
|
||||
video_frame->pid = TS_VIDEO_PID;
|
||||
video_frame->sid = TS_VIDEO_AVC;
|
||||
video_frame->key = sample->frame_type == SrsCodecVideoAVCFrameKeyFrame;
|
||||
|
||||
static u_int8_t aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 };
|
||||
video_buffer->append(aud_nal, sizeof(aud_nal));
|
||||
|
||||
|
|
|
@ -30,34 +30,88 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#include <srs_core.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class SrsOnMetaDataPacket;
|
||||
class SrsSharedPtrMessage;
|
||||
class SrsCodecSample;
|
||||
class SrsCodecBuffer;
|
||||
class SrsMpegtsFrame;
|
||||
class SrsRtmpJitter;
|
||||
class SrsTSMuxer;
|
||||
class SrsCodec;
|
||||
class SrsRtmpJitter;
|
||||
|
||||
/**
|
||||
* 3.3.2. EXTINF
|
||||
* The EXTINF tag specifies the duration of a media segment.
|
||||
*/
|
||||
struct SrsM3u8Segment
|
||||
{
|
||||
// duration in seconds in m3u8.
|
||||
double duration;
|
||||
// sequence number in m3u8.
|
||||
int sequence_no;
|
||||
// ts uri in m3u8.
|
||||
std::string uri;
|
||||
// ts full file to write.
|
||||
std::string full_path;
|
||||
// the muxer to write ts.
|
||||
SrsTSMuxer* muxer;
|
||||
// current segment start dts for m3u8
|
||||
int64_t segment_start_dts;
|
||||
|
||||
SrsM3u8Segment();
|
||||
virtual ~SrsM3u8Segment();
|
||||
};
|
||||
|
||||
/**
|
||||
* write m3u8 hls.
|
||||
*/
|
||||
class SrsHLS
|
||||
{
|
||||
private:
|
||||
std::string vhost;
|
||||
std::string stream;
|
||||
std::string hls_path;
|
||||
private:
|
||||
int file_index;
|
||||
std::string m3u8;
|
||||
private:
|
||||
/**
|
||||
* m3u8 segments.
|
||||
*/
|
||||
std::vector<SrsM3u8Segment*> segments;
|
||||
/**
|
||||
* current writing segment.
|
||||
*/
|
||||
SrsM3u8Segment* current;
|
||||
// current frame and buffer
|
||||
SrsMpegtsFrame* audio_frame;
|
||||
SrsCodecBuffer* audio_buffer;
|
||||
SrsMpegtsFrame* video_frame;
|
||||
SrsCodecBuffer* video_buffer;
|
||||
// last known dts
|
||||
int64_t stream_dts;
|
||||
// last segment dts in m3u8
|
||||
int64_t m3u8_dts;
|
||||
private:
|
||||
bool hls_enabled;
|
||||
SrsCodec* codec;
|
||||
SrsCodecSample* sample;
|
||||
SrsTSMuxer* muxer;
|
||||
SrsRtmpJitter* jitter;
|
||||
public:
|
||||
SrsHLS();
|
||||
virtual ~SrsHLS();
|
||||
public:
|
||||
virtual int on_publish(std::string _vhost);
|
||||
virtual int on_publish(std::string _vhost, std::string _stream);
|
||||
virtual void on_unpublish();
|
||||
virtual int on_meta_data(SrsOnMetaDataPacket* metadata);
|
||||
virtual int on_audio(SrsSharedPtrMessage* audio);
|
||||
virtual int on_video(SrsSharedPtrMessage* video);
|
||||
private:
|
||||
virtual int reopen();
|
||||
virtual int refresh_m3u8();
|
||||
virtual int _refresh_m3u8(int& fd);
|
||||
};
|
||||
|
||||
class SrsTSMuxer
|
||||
|
@ -65,18 +119,13 @@ class SrsTSMuxer
|
|||
private:
|
||||
int fd;
|
||||
std::string path;
|
||||
private:
|
||||
SrsMpegtsFrame* audio_frame;
|
||||
SrsCodecBuffer* audio_buffer;
|
||||
SrsMpegtsFrame* video_frame;
|
||||
SrsCodecBuffer* video_buffer;
|
||||
public:
|
||||
SrsTSMuxer();
|
||||
virtual ~SrsTSMuxer();
|
||||
public:
|
||||
virtual int open(std::string _path);
|
||||
virtual int write_audio(u_int32_t time, SrsCodec* codec, SrsCodecSample* sample);
|
||||
virtual int write_video(u_int32_t time, SrsCodec* codec, SrsCodecSample* sample);
|
||||
virtual int write_audio(SrsMpegtsFrame* audio_frame, SrsCodecBuffer* audio_buffer, SrsCodec* codec, SrsCodecSample* sample);
|
||||
virtual int write_video(SrsMpegtsFrame* video_frame, SrsCodecBuffer* video_buffer, SrsCodec* codec, SrsCodecSample* sample);
|
||||
virtual void close();
|
||||
};
|
||||
|
||||
|
|
|
@ -413,6 +413,7 @@ int SrsSource::on_video(SrsCommonMessage* video)
|
|||
}
|
||||
srs_verbose("initialize shared ptr video success.");
|
||||
|
||||
// TODO: when return error, crash.
|
||||
if ((ret = hls->on_video(msg->copy())) != ERROR_SUCCESS) {
|
||||
srs_error("hls process video message failed. ret=%d", ret);
|
||||
return ret;
|
||||
|
@ -451,9 +452,9 @@ int SrsSource::on_video(SrsCommonMessage* video)
|
|||
return ret;
|
||||
}
|
||||
|
||||
int SrsSource::on_publish(std::string vhost)
|
||||
int SrsSource::on_publish(std::string vhost, std::string stream)
|
||||
{
|
||||
return hls->on_publish(vhost);
|
||||
return hls->on_publish(vhost, stream);
|
||||
}
|
||||
|
||||
void SrsSource::on_unpublish()
|
||||
|
|
|
@ -166,7 +166,7 @@ public:
|
|||
virtual int on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata);
|
||||
virtual int on_audio(SrsCommonMessage* audio);
|
||||
virtual int on_video(SrsCommonMessage* video);
|
||||
virtual int on_publish(std::string vhost);
|
||||
virtual int on_publish(std::string vhost, std::string stream);
|
||||
virtual void on_unpublish();
|
||||
public:
|
||||
virtual int create_consumer(SrsConsumer*& consumer);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue