AI: Add utest to cover dvr module.

This commit is contained in:
OSSRS-AI 2025-10-07 21:33:57 -04:00 committed by winlin
parent 8ed07e37b4
commit f0d713e574
21 changed files with 2394 additions and 76 deletions

View File

@ -494,6 +494,12 @@ public:
virtual bool get_reduce_sequence_header(std::string vhost) = 0;
virtual bool get_parse_sps(std::string vhost) = 0;
public:
// DVR config
virtual std::string get_dvr_path(std::string vhost) = 0;
virtual int get_dvr_time_jitter(std::string vhost) = 0;
virtual bool get_dvr_wait_keyframe(std::string vhost) = 0;
public:
// HTTP remux config
virtual bool get_vhost_http_remux_enabled(std::string vhost) = 0;
@ -509,6 +515,11 @@ public:
virtual std::string get_vhost_edge_protocol(std::string vhost) = 0;
virtual bool get_vhost_edge_follow_client(std::string vhost) = 0;
virtual std::string get_vhost_edge_transform_vhost(std::string vhost) = 0;
virtual SrsConfDirective *get_vhost_on_dvr(std::string vhost) = 0;
virtual std::string get_dvr_plan(std::string vhost) = 0;
virtual bool get_dvr_enabled(std::string vhost) = 0;
virtual SrsConfDirective *get_dvr_apply(std::string vhost) = 0;
virtual srs_utime_t get_dvr_duration(std::string vhost) = 0;
};
// The config service provider.

View File

@ -25,9 +25,18 @@ using namespace std;
#include <srs_protocol_amf0.hpp>
#include <srs_protocol_json.hpp>
#include <srs_protocol_rtmp_stack.hpp>
#include <srs_app_factory.hpp>
#define SRS_FWRITE_CACHE_SIZE 65536
ISrsDvrSegmenter::ISrsDvrSegmenter()
{
}
ISrsDvrSegmenter::~ISrsDvrSegmenter()
{
}
SrsDvrSegmenter::SrsDvrSegmenter()
{
req_ = NULL;
@ -39,16 +48,23 @@ SrsDvrSegmenter::SrsDvrSegmenter()
fs_ = new SrsFileWriter();
jitter_algorithm_ = SrsRtmpJitterAlgorithmOFF;
_srs_config->subscribe(this);
config_ = _srs_config;
}
void SrsDvrSegmenter::assemble()
{
config_->subscribe(this);
}
SrsDvrSegmenter::~SrsDvrSegmenter()
{
_srs_config->unsubscribe(this);
config_->unsubscribe(this);
srs_freep(fragment_);
srs_freep(jitter_);
srs_freep(fs_);
config_ = NULL;
}
// CRITICAL: This method is called AFTER the source has been added to the source pool
@ -57,13 +73,13 @@ SrsDvrSegmenter::~SrsDvrSegmenter()
// IMPORTANT: All field initialization in this method MUST NOT cause coroutine context switches.
// This prevents the race condition where multiple coroutines could create duplicate sources
// for the same stream when context switches occurred during initialization.
srs_error_t SrsDvrSegmenter::initialize(SrsDvrPlan *p, ISrsRequest *r)
srs_error_t SrsDvrSegmenter::initialize(ISrsDvrPlan *p, ISrsRequest *r)
{
req_ = r;
plan_ = p;
jitter_algorithm_ = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req_->vhost_);
wait_keyframe_ = _srs_config->get_dvr_wait_keyframe(req_->vhost_);
jitter_algorithm_ = (SrsRtmpJitterAlgorithm)config_->get_dvr_time_jitter(req_->vhost_);
wait_keyframe_ = config_->get_dvr_wait_keyframe(req_->vhost_);
return srs_success;
}
@ -201,7 +217,7 @@ string SrsDvrSegmenter::generate_path()
{
// the path in config, for example,
// /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv
std::string path_config = _srs_config->get_dvr_path(req_->vhost_);
std::string path_config = config_->get_dvr_path(req_->vhost_);
// add [stream].[timestamp].flv as filename for dir
if (!srs_strings_ends_with(path_config, ".flv", ".mp4")) {
@ -230,11 +246,15 @@ SrsDvrFlvSegmenter::SrsDvrFlvSegmenter()
filesize_offset_ = 0;
has_keyframe_ = false;
app_factory_ = _srs_app_factory;
}
SrsDvrFlvSegmenter::~SrsDvrFlvSegmenter()
{
srs_freep(enc_);
app_factory_ = NULL;
}
srs_error_t SrsDvrFlvSegmenter::refresh_metadata()
@ -297,7 +317,7 @@ srs_error_t SrsDvrFlvSegmenter::open_encoder()
filesize_offset_ = 0;
srs_freep(enc_);
enc_ = new SrsFlvTransmuxer();
enc_ = app_factory_->create_flv_transmuxer();
if ((err = enc_->initialize(fs_)) != srs_success) {
return srs_error_wrap(err, "init encoder");
@ -414,11 +434,15 @@ srs_error_t SrsDvrFlvSegmenter::close_encoder()
SrsDvrMp4Segmenter::SrsDvrMp4Segmenter()
{
enc_ = new SrsMp4Encoder();
app_factory_ = _srs_app_factory;
}
SrsDvrMp4Segmenter::~SrsDvrMp4Segmenter()
{
srs_freep(enc_);
app_factory_ = NULL;
}
srs_error_t SrsDvrMp4Segmenter::refresh_metadata()
@ -431,7 +455,7 @@ srs_error_t SrsDvrMp4Segmenter::open_encoder()
srs_error_t err = srs_success;
srs_freep(enc_);
enc_ = new SrsMp4Encoder();
enc_ = app_factory_->create_mp4_encoder();
if ((err = enc_->initialize(fs_)) != srs_success) {
return srs_error_wrap(err, "init encoder");
@ -456,10 +480,7 @@ srs_error_t SrsDvrMp4Segmenter::encode_audio(SrsMediaPacket *audio, SrsFormat *f
SrsAudioAacFrameTrait ct = format->audio_->aac_packet_type_;
if (ct == SrsAudioAacFrameTraitSequenceHeader || ct == SrsAudioMp3FrameTraitSequenceHeader) {
enc_->acodec_ = sound_format;
enc_->sample_rate_ = sound_rate;
enc_->sound_bits_ = sound_size;
enc_->channels_ = channels;
enc_->set_audio_codec(sound_format, sound_rate, sound_size, channels);
}
uint8_t *sample = (uint8_t *)format->raw_;
@ -515,18 +536,24 @@ SrsDvrAsyncCallOnDvr::SrsDvrAsyncCallOnDvr(SrsContextId c, ISrsRequest *r, strin
cid_ = c;
req_ = r->copy();
path_ = p;
hooks_ = _srs_hooks;
config_ = _srs_config;
}
SrsDvrAsyncCallOnDvr::~SrsDvrAsyncCallOnDvr()
{
srs_freep(req_);
hooks_ = NULL;
config_ = NULL;
}
srs_error_t SrsDvrAsyncCallOnDvr::call()
{
srs_error_t err = srs_success;
if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost_)) {
if (!config_->get_vhost_http_hooks_enabled(req_->vhost_)) {
return err;
}
@ -536,7 +563,7 @@ srs_error_t SrsDvrAsyncCallOnDvr::call()
vector<string> hooks;
if (true) {
SrsConfDirective *conf = _srs_config->get_vhost_on_dvr(req_->vhost_);
SrsConfDirective *conf = config_->get_vhost_on_dvr(req_->vhost_);
if (conf) {
hooks = conf->args_;
}
@ -544,7 +571,7 @@ srs_error_t SrsDvrAsyncCallOnDvr::call()
for (int i = 0; i < (int)hooks.size(); i++) {
std::string url = hooks.at(i);
if ((err = _srs_hooks->on_dvr(cid_, url, req_, path_)) != srs_success) {
if ((err = hooks_->on_dvr(cid_, url, req_, path_)) != srs_success) {
return srs_error_wrap(err, "callback on_dvr %s", url.c_str());
}
}
@ -559,6 +586,14 @@ string SrsDvrAsyncCallOnDvr::to_string()
return ss.str();
}
ISrsDvrPlan::ISrsDvrPlan()
{
}
ISrsDvrPlan::~ISrsDvrPlan()
{
}
SrsDvrPlan::SrsDvrPlan()
{
req_ = NULL;
@ -566,12 +601,18 @@ SrsDvrPlan::SrsDvrPlan()
dvr_enabled_ = false;
segment_ = NULL;
async_ = _srs_dvr_async;
config_ = _srs_config;
}
SrsDvrPlan::~SrsDvrPlan()
{
srs_freep(segment_);
srs_freep(req_);
async_ = NULL;
config_ = NULL;
}
// CRITICAL: This method is called AFTER the source has been added to the source pool
@ -580,7 +621,7 @@ SrsDvrPlan::~SrsDvrPlan()
// IMPORTANT: All field initialization in this method MUST NOT cause coroutine context switches.
// This prevents the race condition where multiple coroutines could create duplicate sources
// for the same stream when context switches occurred during initialization.
srs_error_t SrsDvrPlan::initialize(SrsOriginHub *h, SrsDvrSegmenter *s, ISrsRequest *r)
srs_error_t SrsDvrPlan::initialize(ISrsOriginHub *h, ISrsDvrSegmenter *s, ISrsRequest *r)
{
srs_error_t err = srs_success;
@ -658,7 +699,7 @@ srs_error_t SrsDvrPlan::on_reap_segment()
SrsFragment *fragment = segment_->current();
string fullpath = fragment->fullpath();
if ((err = _srs_dvr_async->execute(new SrsDvrAsyncCallOnDvr(cid, req_, fullpath))) != srs_success) {
if ((err = async_->execute(new SrsDvrAsyncCallOnDvr(cid, req_, fullpath))) != srs_success) {
return srs_error_wrap(err, "reap segment");
}
@ -671,9 +712,9 @@ srs_error_t SrsDvrPlan::on_reap_segment()
// IMPORTANT: All field initialization in this method MUST NOT cause coroutine context switches.
// This prevents the race condition where multiple coroutines could create duplicate sources
// for the same stream when context switches occurred during initialization.
srs_error_t SrsDvrPlan::create_plan(string vhost, SrsDvrPlan **pplan)
srs_error_t SrsDvrPlan::create_plan(ISrsAppConfig *config, string vhost, ISrsDvrPlan **pplan)
{
std::string plan = _srs_config->get_dvr_plan(vhost);
std::string plan = config->get_dvr_plan(vhost);
if (srs_config_dvr_is_plan_segment(plan)) {
*pplan = new SrsDvrSegmentPlan();
} else if (srs_config_dvr_is_plan_session(plan)) {
@ -707,7 +748,7 @@ srs_error_t SrsDvrSessionPlan::on_publish(ISrsRequest *r)
return err;
}
if (!_srs_config->get_dvr_enabled(req_->vhost_)) {
if (!config_->get_dvr_enabled(req_->vhost_)) {
return err;
}
@ -756,7 +797,7 @@ SrsDvrSegmentPlan::~SrsDvrSegmentPlan()
{
}
srs_error_t SrsDvrSegmentPlan::initialize(SrsOriginHub *h, SrsDvrSegmenter *s, ISrsRequest *r)
srs_error_t SrsDvrSegmentPlan::initialize(ISrsOriginHub *h, ISrsDvrSegmenter *s, ISrsRequest *r)
{
srs_error_t err = srs_success;
@ -764,9 +805,9 @@ srs_error_t SrsDvrSegmentPlan::initialize(SrsOriginHub *h, SrsDvrSegmenter *s, I
return srs_error_wrap(err, "segment plan");
}
wait_keyframe_ = _srs_config->get_dvr_wait_keyframe(req_->vhost_);
wait_keyframe_ = config_->get_dvr_wait_keyframe(req_->vhost_);
cduration_ = _srs_config->get_dvr_duration(req_->vhost_);
cduration_ = config_->get_dvr_duration(req_->vhost_);
return srs_success;
}
@ -784,7 +825,7 @@ srs_error_t SrsDvrSegmentPlan::on_publish(ISrsRequest *r)
return err;
}
if (!_srs_config->get_dvr_enabled(req_->vhost_)) {
if (!config_->get_dvr_enabled(req_->vhost_)) {
return err;
}
@ -919,15 +960,24 @@ SrsDvr::SrsDvr()
req_ = NULL;
actived_ = false;
_srs_config->subscribe(this);
config_ = _srs_config;
app_factory_ = _srs_app_factory;
}
void SrsDvr::assemble()
{
config_->subscribe(this);
}
SrsDvr::~SrsDvr()
{
_srs_config->unsubscribe(this);
config_->unsubscribe(this);
srs_freep(plan_);
srs_freep(req_);
config_ = NULL;
app_factory_ = NULL;
}
// CRITICAL: This method is called AFTER the source has been added to the source pool
@ -936,28 +986,29 @@ SrsDvr::~SrsDvr()
// IMPORTANT: All field initialization in this method MUST NOT cause coroutine context switches.
// This prevents the race condition where multiple coroutines could create duplicate sources
// for the same stream when context switches occurred during initialization.
srs_error_t SrsDvr::initialize(SrsOriginHub *h, ISrsRequest *r)
srs_error_t SrsDvr::initialize(ISrsOriginHub *h, ISrsRequest *r)
{
srs_error_t err = srs_success;
req_ = r->copy();
hub_ = h;
SrsConfDirective *conf = _srs_config->get_dvr_apply(r->vhost_);
SrsConfDirective *conf = config_->get_dvr_apply(r->vhost_);
actived_ = srs_config_apply_filter(conf, r);
srs_freep(plan_);
if ((err = SrsDvrPlan::create_plan(r->vhost_, &plan_)) != srs_success) {
if ((err = SrsDvrPlan::create_plan(config_, r->vhost_, &plan_)) != srs_success) {
return srs_error_wrap(err, "create plan");
}
std::string path = _srs_config->get_dvr_path(r->vhost_);
SrsDvrSegmenter *segmenter = NULL;
std::string path = config_->get_dvr_path(r->vhost_);
ISrsDvrSegmenter *segmenter = NULL;
if (srs_strings_ends_with(path, ".mp4")) {
segmenter = new SrsDvrMp4Segmenter();
segmenter = app_factory_->create_dvr_mp4_segmenter();
} else {
segmenter = new SrsDvrFlvSegmenter();
segmenter = app_factory_->create_dvr_flv_segmenter();
}
segmenter->assemble();
if ((err = plan_->initialize(hub_, segmenter, r)) != srs_success) {
return srs_error_wrap(err, "plan initialize");

View File

@ -27,17 +27,45 @@ class SrsThread;
class SrsMp4Encoder;
class SrsFragment;
class SrsFormat;
class ISrsFileWriter;
class ISrsDvrPlan;
class ISrsMp4Encoder;
class ISrsOriginHub;
class ISrsAppConfig;
class ISrsAppFactory;
class ISrsAsyncCallWorker;
#include <srs_app_async_call.hpp>
#include <srs_app_reload.hpp>
#include <srs_app_rtmp_source.hpp>
// The segmenter for DVR, to write a segment file in flv/mp4.
class SrsDvrSegmenter : public ISrsReloadHandler
// The segmenter interface.
class ISrsDvrSegmenter
{
public:
ISrsDvrSegmenter();
virtual void assemble() = 0;
virtual ~ISrsDvrSegmenter();
public:
virtual srs_error_t initialize(ISrsDvrPlan *p, ISrsRequest *r) = 0;
virtual SrsFragment *current() = 0;
virtual srs_error_t open() = 0;
virtual srs_error_t write_metadata(SrsMediaPacket *metadata) = 0;
virtual srs_error_t write_audio(SrsMediaPacket *shared_audio, SrsFormat *format) = 0;
virtual srs_error_t write_video(SrsMediaPacket *shared_video, SrsFormat *format) = 0;
virtual srs_error_t close() = 0;
};
// The segmenter for DVR, to write a segment file in flv/mp4.
class SrsDvrSegmenter : public ISrsReloadHandler, public ISrsDvrSegmenter
{
private:
ISrsAppConfig *config_;
protected:
// The underlayer file object.
SrsFileWriter *fs_;
ISrsFileWriter *fs_;
// Whether wait keyframe to reap segment.
bool wait_keyframe_;
// The FLV/MP4 fragment file.
@ -45,7 +73,7 @@ protected:
private:
ISrsRequest *req_;
SrsDvrPlan *plan_;
ISrsDvrPlan *plan_;
private:
SrsRtmpJitter *jitter_;
@ -53,11 +81,12 @@ private:
public:
SrsDvrSegmenter();
void assemble();
virtual ~SrsDvrSegmenter();
public:
// Initialize the segment.
virtual srs_error_t initialize(SrsDvrPlan *p, ISrsRequest *r);
virtual srs_error_t initialize(ISrsDvrPlan *p, ISrsRequest *r);
// Get the current framgnet.
virtual SrsFragment *current();
// Open new segment file.
@ -97,6 +126,9 @@ private:
// The FLV segmenter to use FLV encoder to write file.
class SrsDvrFlvSegmenter : public SrsDvrSegmenter
{
private:
ISrsAppFactory *app_factory_;
private:
// The FLV encoder, for FLV target.
ISrsFlvTransmuxer *enc_;
@ -129,9 +161,12 @@ protected:
// The MP4 segmenter to use MP4 encoder to write file.
class SrsDvrMp4Segmenter : public SrsDvrSegmenter
{
private:
ISrsAppFactory *app_factory_;
private:
// The MP4 encoder, for MP4 target.
SrsMp4Encoder *enc_;
ISrsMp4Encoder *enc_;
public:
SrsDvrMp4Segmenter();
@ -151,6 +186,10 @@ protected:
// the dvr async call.
class SrsDvrAsyncCallOnDvr : public ISrsAsyncCallTask
{
private:
ISrsHttpHooks *hooks_;
ISrsAppConfig *config_;
private:
SrsContextId cid_;
std::string path_;
@ -165,15 +204,36 @@ public:
virtual std::string to_string();
};
// The DVR plan, when and how to reap segment.
class SrsDvrPlan : public ISrsReloadHandler
// The DVR plan interface.
class ISrsDvrPlan
{
public:
ISrsDvrPlan();
virtual ~ISrsDvrPlan();
public:
virtual srs_error_t initialize(ISrsOriginHub *h, ISrsDvrSegmenter *s, ISrsRequest *r) = 0;
virtual srs_error_t on_publish(ISrsRequest *r) = 0;
virtual void on_unpublish() = 0;
virtual srs_error_t on_meta_data(SrsMediaPacket *shared_metadata) = 0;
virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format) = 0;
virtual srs_error_t on_video(SrsMediaPacket *shared_video, SrsFormat *format) = 0;
virtual srs_error_t on_reap_segment() = 0;
};
// The DVR plan, when and how to reap segment.
class SrsDvrPlan : public ISrsReloadHandler, public ISrsDvrPlan
{
protected:
ISrsAsyncCallWorker *async_;
ISrsAppConfig *config_;
public:
ISrsRequest *req_;
protected:
SrsOriginHub *hub_;
SrsDvrSegmenter *segment_;
ISrsOriginHub *hub_;
ISrsDvrSegmenter *segment_;
bool dvr_enabled_;
public:
@ -181,7 +241,7 @@ public:
virtual ~SrsDvrPlan();
public:
virtual srs_error_t initialize(SrsOriginHub *h, SrsDvrSegmenter *s, ISrsRequest *r);
virtual srs_error_t initialize(ISrsOriginHub *h, ISrsDvrSegmenter *s, ISrsRequest *r);
virtual srs_error_t on_publish(ISrsRequest *r);
virtual void on_unpublish();
virtual srs_error_t on_meta_data(SrsMediaPacket *shared_metadata);
@ -193,7 +253,7 @@ public:
virtual srs_error_t on_reap_segment();
public:
static srs_error_t create_plan(std::string vhost, SrsDvrPlan **pplan);
static srs_error_t create_plan(ISrsAppConfig *config, std::string vhost, ISrsDvrPlan **pplan);
};
// The DVR session plan: reap flv when session complete(unpublish)
@ -223,7 +283,7 @@ public:
virtual ~SrsDvrSegmentPlan();
public:
virtual srs_error_t initialize(SrsOriginHub *h, SrsDvrSegmenter *s, ISrsRequest *r);
virtual srs_error_t initialize(ISrsOriginHub *h, ISrsDvrSegmenter *s, ISrsRequest *r);
virtual srs_error_t on_publish(ISrsRequest *r);
virtual void on_unpublish();
virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format);
@ -238,10 +298,11 @@ class ISrsDvr
{
public:
ISrsDvr();
virtual void assemble() = 0;
virtual ~ISrsDvr();
public:
virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r) = 0;
virtual srs_error_t initialize(ISrsOriginHub *h, ISrsRequest *r) = 0;
virtual srs_error_t on_publish(ISrsRequest *r) = 0;
virtual void on_unpublish() = 0;
virtual srs_error_t on_meta_data(SrsMediaPacket *metadata) = 0;
@ -253,8 +314,12 @@ public:
class SrsDvr : public ISrsReloadHandler, public ISrsDvr
{
private:
SrsOriginHub *hub_;
SrsDvrPlan *plan_;
ISrsAppConfig *config_;
ISrsAppFactory *app_factory_;
private:
ISrsOriginHub *hub_;
ISrsDvrPlan *plan_;
ISrsRequest *req_;
private:
@ -265,13 +330,14 @@ private:
public:
SrsDvr();
void assemble();
virtual ~SrsDvr();
public:
// initialize dvr, create dvr plan.
// when system initialize(encoder publish at first time, or reload),
// initialize the dvr will reinitialize the plan, the whole dvr framework.
virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r);
virtual srs_error_t initialize(ISrsOriginHub *h, ISrsRequest *r);
// publish stream event,
// when encoder start to publish RTMP stream.
// @param fetch_sequence_header whether fetch sequence from source.

View File

@ -19,6 +19,9 @@
#include <srs_protocol_http_client.hpp>
#include <srs_protocol_st.hpp>
#include <srs_app_rtsp_source.hpp>
#include <srs_kernel_flv.hpp>
#include <srs_kernel_mp4.hpp>
#include <srs_app_dvr.hpp>
ISrsAppFactory::ISrsAppFactory()
{
@ -93,6 +96,7 @@ ISrsFlvDecoder *SrsAppFactory::create_flv_decoder()
return new SrsFlvDecoder();
}
#ifdef SRS_RTSP
ISrsRtspSendTrack *SrsAppFactory::create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc)
{
return new SrsRtspAudioSendTrack(session, track_desc);
@ -102,6 +106,27 @@ ISrsRtspSendTrack *SrsAppFactory::create_rtsp_video_send_track(ISrsRtspConnectio
{
return new SrsRtspVideoSendTrack(session, track_desc);
}
#endif
ISrsFlvTransmuxer *SrsAppFactory::create_flv_transmuxer()
{
return new SrsFlvTransmuxer();
}
ISrsMp4Encoder *SrsAppFactory::create_mp4_encoder()
{
return new SrsMp4Encoder();
}
ISrsDvrSegmenter *SrsAppFactory::create_dvr_flv_segmenter()
{
return new SrsDvrFlvSegmenter();
}
ISrsDvrSegmenter *SrsAppFactory::create_dvr_mp4_segmenter()
{
return new SrsDvrMp4Segmenter();
}
SrsFinalFactory::SrsFinalFactory()
{

View File

@ -26,6 +26,9 @@ class ISrsHttpResponseReader;
class ISrsRtspSendTrack;
class ISrsRtspConnection;
class SrsRtcTrackDescription;
class ISrsFlvTransmuxer;
class ISrsMp4Encoder;
class ISrsDvrSegmenter;
// The factory to create app objects.
class ISrsAppFactory
@ -46,8 +49,14 @@ public:
virtual ISrsHttpClient *create_http_client() = 0;
virtual ISrsFileReader *create_http_file_reader(ISrsHttpResponseReader *r) = 0;
virtual ISrsFlvDecoder *create_flv_decoder() = 0;
#ifdef SRS_RTSP
virtual ISrsRtspSendTrack *create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) = 0;
virtual ISrsRtspSendTrack *create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) = 0;
#endif
virtual ISrsFlvTransmuxer *create_flv_transmuxer() = 0;
virtual ISrsMp4Encoder *create_mp4_encoder() = 0;
virtual ISrsDvrSegmenter *create_dvr_flv_segmenter() = 0;
virtual ISrsDvrSegmenter *create_dvr_mp4_segmenter() = 0;
};
// The factory to create app objects.
@ -69,8 +78,14 @@ public:
virtual ISrsHttpClient *create_http_client();
virtual ISrsFileReader *create_http_file_reader(ISrsHttpResponseReader *r);
virtual ISrsFlvDecoder *create_flv_decoder();
#ifdef SRS_RTSP
virtual ISrsRtspSendTrack *create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc);
virtual ISrsRtspSendTrack *create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc);
#endif
virtual ISrsFlvTransmuxer *create_flv_transmuxer();
virtual ISrsMp4Encoder *create_mp4_encoder();
virtual ISrsDvrSegmenter *create_dvr_flv_segmenter();
virtual ISrsDvrSegmenter *create_dvr_mp4_segmenter();
};
extern ISrsAppFactory *_srs_app_factory;

View File

@ -244,7 +244,9 @@ void SrsRtmpConn::assemble()
SrsRtmpConn::~SrsRtmpConn()
{
config_->unsubscribe(this);
if (config_) {
config_->unsubscribe(this);
}
trd_->interrupt();
// wakeup the handler which need to notice.
@ -262,6 +264,7 @@ SrsRtmpConn::~SrsRtmpConn()
srs_freep(refer_);
srs_freep(security_);
config_ = NULL;
manager_ = NULL;
stream_publish_tokens_ = NULL;
live_sources_ = NULL;

View File

@ -850,7 +850,10 @@ SrsOriginHub::SrsOriginHub()
hls_ = new SrsHls();
dash_ = new SrsDash();
dvr_ = new SrsDvr();
dvr_->assemble();
encoder_ = new SrsEncoder();
#ifdef SRS_HDS
hds_ = new SrsHds();

View File

@ -402,6 +402,8 @@ public:
virtual srs_error_t on_publish() = 0;
// When stop publish stream.
virtual void on_unpublish() = 0;
// When DVR requests sequence header.
virtual srs_error_t on_dvr_request_sh() = 0;
};
// The hub for origin is a collection of utilities for origin only,

View File

@ -30,6 +30,9 @@ public:
virtual srs_error_t open(std::string p) = 0;
virtual void close() = 0;
virtual bool is_open() = 0;
virtual srs_error_t set_iobuf_size(int size) = 0;
virtual void seek2(int64_t offset) = 0;
virtual int64_t tellg() = 0;
};
// file writer, to write to file.

View File

@ -6493,6 +6493,14 @@ srs_error_t SrsMp4Decoder::do_load_next_box(SrsMp4Box **ppbox, uint32_t required
return err;
}
ISrsMp4Encoder::ISrsMp4Encoder()
{
}
ISrsMp4Encoder::~ISrsMp4Encoder()
{
}
SrsMp4Encoder::SrsMp4Encoder()
{
wsio_ = NULL;
@ -6889,6 +6897,14 @@ srs_error_t SrsMp4Encoder::flush()
return err;
}
void SrsMp4Encoder::set_audio_codec(SrsAudioCodecId vcodec, SrsAudioSampleRate sample_rate, SrsAudioSampleBits sound_bits, SrsAudioChannels channels)
{
acodec_ = vcodec;
sample_rate_ = sample_rate;
sound_bits_ = sound_bits;
channels_ = channels;
}
srs_error_t SrsMp4Encoder::copy_sequence_header(SrsFormat *format, bool vsh, uint8_t *sample, uint32_t nb_sample)
{
srs_error_t err = srs_success;

View File

@ -2624,8 +2624,27 @@ private:
virtual srs_error_t do_load_next_box(SrsMp4Box **ppbox, uint32_t required_box_type);
};
// The MP4 encoder interface.
class ISrsMp4Encoder
{
public:
ISrsMp4Encoder();
virtual ~ISrsMp4Encoder();
public:
// The video codec of first track.
SrsVideoCodecId vcodec_;
public:
virtual srs_error_t initialize(ISrsWriteSeeker *ws) = 0;
virtual srs_error_t write_sample(SrsFormat *format, SrsMp4HandlerType ht, uint16_t ft, uint16_t ct,
uint32_t dts, uint32_t pts, uint8_t *sample, uint32_t nb_sample) = 0;
virtual srs_error_t flush() = 0;
virtual void set_audio_codec(SrsAudioCodecId vcodec, SrsAudioSampleRate sample_rate, SrsAudioSampleBits sound_bits, SrsAudioChannels channels) = 0;
};
// The MP4 muxer.
class SrsMp4Encoder
class SrsMp4Encoder : public ISrsMp4Encoder
{
private:
ISrsWriteSeeker *wsio_;
@ -2694,6 +2713,8 @@ public:
// Flush the encoder, to write the moov.
virtual srs_error_t flush();
virtual void set_audio_codec(SrsAudioCodecId vcodec, SrsAudioSampleRate sample_rate, SrsAudioSampleBits sound_bits, SrsAudioChannels channels);
private:
virtual srs_error_t copy_sequence_header(SrsFormat *format, bool vsh, uint8_t *sample, uint32_t nb_sample);
virtual srs_error_t do_write_sample(SrsMp4Sample *ps, uint8_t *sample, uint32_t nb_sample);

View File

@ -228,9 +228,6 @@ VOID TEST(SrsServerTest, ListenRtmpSuccess)
// - Socket binding succeeded on the random port
// - Connection manager started successfully
EXPECT_TRUE(server.get() != NULL);
// Cleanup: restore original config to avoid side effects
server->config_ = _srs_config;
}
MockHttpServeMux::MockHttpServeMux()
@ -1713,7 +1710,7 @@ VOID TEST(SrsRtmpConnTest, StreamServiceCycleSelection)
// Test srs_get_disk_diskstats_stat() function to verify proper disk statistics
// collection from /proc/diskstats. This test covers the major use scenario of
// reading disk I/O statistics for configured disk devices. The function uses
// the global _srs_config to get disk device configuration.
// the global config to get disk device configuration.
VOID TEST(SrsUtilityTest, GetDiskDiskstatsStat)
{
// Test case 1: Call with default config - should return true with ok_ = true
@ -2879,9 +2876,9 @@ VOID TEST(SrsRtmpConnTest, HttpHooksOnClose)
EXPECT_EQ(0, mock_hooks->on_close_calls_[1].send_bytes_);
EXPECT_EQ(0, mock_hooks->on_close_calls_[1].recv_bytes_);
// Cleanup: restore original config and hooks to avoid side effects
conn->config_ = _srs_config;
conn->hooks_ = _srs_hooks;
// Clean up injected dependencies to avoid double-free
conn->config_ = NULL;
conn->hooks_ = NULL;
srs_freep(mock_config);
srs_freep(mock_hooks);
}
@ -3033,9 +3030,9 @@ VOID TEST(SrsRtmpConnTest, HttpHooksOnPublishSuccess)
EXPECT_STREQ("http://localhost:8085/api/v1/publish", mock_hooks->on_publish_calls_[1].first.c_str());
EXPECT_TRUE(mock_hooks->on_publish_calls_[1].second == conn->info_->req_);
// Cleanup: restore original config and hooks to avoid side effects
conn->config_ = _srs_config;
conn->hooks_ = _srs_hooks;
// Clean up injected dependencies to avoid double-free
conn->config_ = NULL;
conn->hooks_ = NULL;
srs_freep(mock_config);
srs_freep(mock_hooks);
}
@ -3114,9 +3111,9 @@ VOID TEST(SrsRtmpConnTest, HttpHooksOnUnpublishSuccess)
EXPECT_STREQ("http://localhost:8085/api/v1/unpublish", mock_hooks->on_unpublish_calls_[1].first.c_str());
EXPECT_TRUE(mock_hooks->on_unpublish_calls_[1].second == conn->info_->req_);
// Cleanup: restore original config and hooks to avoid side effects
conn->config_ = _srs_config;
conn->hooks_ = _srs_hooks;
// Clean up injected dependencies to avoid double-free
conn->config_ = NULL;
conn->hooks_ = NULL;
srs_freep(mock_config);
srs_freep(mock_hooks);
}
@ -3195,9 +3192,9 @@ VOID TEST(SrsRtmpConnTest, HttpHooksOnStopSuccess)
EXPECT_STREQ("http://localhost:8085/api/v1/stop", mock_hooks->on_stop_calls_[1].first.c_str());
EXPECT_TRUE(mock_hooks->on_stop_calls_[1].second == conn->info_->req_);
// Cleanup: restore original config and hooks to avoid side effects
conn->config_ = _srs_config;
conn->hooks_ = _srs_hooks;
// Clean up injected dependencies to avoid double-free
conn->config_ = NULL;
conn->hooks_ = NULL;
srs_freep(mock_config);
srs_freep(mock_hooks);
}
@ -3349,9 +3346,9 @@ VOID TEST(SrsRtmpConnTest, HttpHooksOnPlaySuccess)
EXPECT_STREQ("http://localhost:8085/api/v1/play", mock_hooks->on_play_calls_[1].first.c_str());
EXPECT_TRUE(mock_hooks->on_play_calls_[1].second == conn->info_->req_);
// Cleanup: restore original config and hooks to avoid side effects
conn->config_ = _srs_config;
conn->hooks_ = _srs_hooks;
// Clean up injected dependencies to avoid double-free
conn->config_ = NULL;
conn->hooks_ = NULL;
srs_freep(mock_config);
srs_freep(mock_hooks);
}

View File

@ -2452,9 +2452,6 @@ VOID TEST(SrsRtspRtpBuilderTest, InitializePublishUnpublishLifecycle)
EXPECT_EQ(15, builder->meta_->previous_ash()->size());
EXPECT_EQ(0, memcmp(builder->meta_->previous_vsh()->payload(), video_data, 20));
EXPECT_EQ(0, memcmp(builder->meta_->previous_ash()->payload(), audio_data, 15));
// Restore global config
builder->config_ = _srs_config;
}
// Test SrsRtspRtpBuilder::on_frame and on_audio - covers the major use scenario:

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@
#include <srs_utest.hpp>
#include <srs_app_config.hpp>
#include <srs_app_dvr.hpp>
#include <srs_app_edge.hpp>
#include <srs_app_factory.hpp>
#include <srs_app_rtc_source.hpp>
@ -22,6 +23,7 @@
#include <srs_app_statistic.hpp>
#include <srs_kernel_balance.hpp>
#include <srs_kernel_flv.hpp>
#include <srs_kernel_mp4.hpp>
#include <srs_protocol_http_client.hpp>
#include <srs_protocol_http_stack.hpp>
#include <srs_protocol_json.hpp>
@ -64,6 +66,14 @@ public:
virtual int get_chunk_size(std::string vhost);
virtual srs_utime_t get_vhost_edge_origin_connect_timeout(std::string vhost);
virtual srs_utime_t get_vhost_edge_origin_stream_timeout(std::string vhost);
// DVR methods
virtual std::string get_dvr_path(std::string vhost);
virtual int get_dvr_time_jitter(std::string vhost);
virtual bool get_dvr_wait_keyframe(std::string vhost);
virtual bool get_dvr_enabled(std::string vhost);
virtual srs_utime_t get_dvr_duration(std::string vhost);
virtual SrsConfDirective *get_dvr_apply(std::string vhost);
virtual std::string get_dvr_plan(std::string vhost);
};
// Mock RTMP client for testing edge upstream
@ -478,4 +488,202 @@ public:
void reset();
};
// Mock ISrsDvrPlan for testing SrsDvrSegmenter
class MockDvrPlan : public ISrsDvrPlan
{
public:
bool on_publish_called_;
bool on_unpublish_called_;
srs_error_t on_publish_error_;
public:
MockDvrPlan();
virtual ~MockDvrPlan();
public:
virtual srs_error_t initialize(ISrsOriginHub *h, ISrsDvrSegmenter *s, ISrsRequest *r);
virtual srs_error_t on_publish(ISrsRequest *r);
virtual void on_unpublish();
virtual srs_error_t on_meta_data(SrsMediaPacket *shared_metadata);
virtual srs_error_t on_audio(SrsMediaPacket *shared_audio, SrsFormat *format);
virtual srs_error_t on_video(SrsMediaPacket *shared_video, SrsFormat *format);
virtual srs_error_t on_reap_segment();
};
// Mock ISrsHttpHooks for testing SrsDvrAsyncCallOnDvr
class MockHttpHooksForDvrAsyncCall : public ISrsHttpHooks
{
public:
struct OnDvrCall {
SrsContextId cid_;
std::string url_;
ISrsRequest *req_;
std::string file_;
};
std::vector<OnDvrCall> on_dvr_calls_;
int on_dvr_count_;
srs_error_t on_dvr_error_;
public:
MockHttpHooksForDvrAsyncCall();
virtual ~MockHttpHooksForDvrAsyncCall();
public:
virtual srs_error_t on_connect(std::string url, ISrsRequest *req);
virtual void on_close(std::string url, ISrsRequest *req, int64_t send_bytes, int64_t recv_bytes);
virtual srs_error_t on_publish(std::string url, ISrsRequest *req);
virtual void on_unpublish(std::string url, ISrsRequest *req);
virtual srs_error_t on_play(std::string url, ISrsRequest *req);
virtual void on_stop(std::string url, ISrsRequest *req);
virtual srs_error_t on_dvr(SrsContextId cid, std::string url, ISrsRequest *req, std::string file);
virtual srs_error_t on_hls(SrsContextId cid, std::string url, ISrsRequest *req, std::string file, std::string ts_url,
std::string m3u8, std::string m3u8_url, int sn, srs_utime_t duration);
virtual srs_error_t on_hls_notify(SrsContextId cid, std::string url, ISrsRequest *req, std::string ts_url, int nb_notify);
virtual srs_error_t discover_co_workers(std::string url, std::string &host, int &port);
virtual srs_error_t on_forward_backend(std::string url, ISrsRequest *req, std::vector<std::string> &rtmp_urls);
void reset();
};
// Mock ISrsFlvTransmuxer for testing SrsDvrFlvSegmenter
class MockFlvTransmuxer : public ISrsFlvTransmuxer
{
public:
bool write_header_called_;
bool write_metadata_called_;
char metadata_type_;
int metadata_size_;
srs_error_t write_metadata_error_;
public:
MockFlvTransmuxer();
virtual ~MockFlvTransmuxer();
public:
virtual srs_error_t initialize(ISrsWriter *fw);
virtual void set_drop_if_not_match(bool v);
virtual bool drop_if_not_match();
virtual srs_error_t write_header(bool has_video = true, bool has_audio = true);
virtual srs_error_t write_header(char flv_header[9]);
virtual srs_error_t write_metadata(char type, char *data, int size);
virtual srs_error_t write_audio(int64_t timestamp, char *data, int size);
virtual srs_error_t write_video(int64_t timestamp, char *data, int size);
virtual srs_error_t write_tags(SrsMediaPacket **msgs, int count);
};
// Mock ISrsMp4Encoder for testing SrsDvrMp4Segmenter
class MockMp4Encoder : public ISrsMp4Encoder
{
public:
bool initialize_called_;
bool write_sample_called_;
bool flush_called_;
bool set_audio_codec_called_;
SrsMp4HandlerType last_handler_type_;
uint16_t last_frame_type_;
uint16_t last_codec_type_;
uint32_t last_dts_;
uint32_t last_pts_;
uint32_t last_sample_size_;
SrsAudioCodecId last_audio_codec_;
SrsAudioSampleRate last_audio_sample_rate_;
SrsAudioSampleBits last_audio_sound_bits_;
SrsAudioChannels last_audio_channels_;
public:
MockMp4Encoder();
virtual ~MockMp4Encoder();
public:
virtual srs_error_t initialize(ISrsWriteSeeker *ws);
virtual srs_error_t write_sample(SrsFormat *format, SrsMp4HandlerType ht, uint16_t ft, uint16_t ct,
uint32_t dts, uint32_t pts, uint8_t *sample, uint32_t nb_sample);
virtual srs_error_t flush();
virtual void set_audio_codec(SrsAudioCodecId vcodec, SrsAudioSampleRate sample_rate, SrsAudioSampleBits sound_bits, SrsAudioChannels channels);
void reset();
};
// Mock ISrsAppFactory for testing SrsDvrMp4Segmenter
class MockDvrAppFactory : public ISrsAppFactory
{
public:
MockMp4Encoder *mock_mp4_encoder_;
public:
MockDvrAppFactory();
virtual ~MockDvrAppFactory();
public:
virtual ISrsFileWriter *create_file_writer();
virtual ISrsFileWriter *create_enc_file_writer();
virtual ISrsFileReader *create_file_reader();
virtual SrsPath *create_path();
virtual SrsLiveSource *create_live_source();
virtual ISrsOriginHub *create_origin_hub();
virtual ISrsHourGlass *create_hourglass(const std::string &name, ISrsHourGlassHandler *handler, srs_utime_t interval);
virtual ISrsBasicRtmpClient *create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto);
virtual ISrsHttpClient *create_http_client();
virtual ISrsHttpResponseReader *create_http_response_reader(ISrsHttpResponseReader *r);
virtual ISrsFileReader *create_http_file_reader(ISrsHttpResponseReader *r);
virtual ISrsFlvDecoder *create_flv_decoder();
virtual ISrsBasicRtmpClient *create_basic_rtmp_client(std::string url, srs_utime_t ctm, srs_utime_t stm);
#ifdef SRS_RTSP
virtual ISrsRtspSendTrack *create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc);
virtual ISrsRtspSendTrack *create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc);
#endif
virtual ISrsFlvTransmuxer *create_flv_transmuxer();
virtual ISrsMp4Encoder *create_mp4_encoder();
virtual ISrsDvrSegmenter *create_dvr_flv_segmenter();
virtual ISrsDvrSegmenter *create_dvr_mp4_segmenter();
};
// Mock ISrsDvrSegmenter for testing SrsDvrPlan
class MockDvrSegmenter : public ISrsDvrSegmenter
{
public:
bool write_metadata_called_;
bool write_audio_called_;
bool write_video_called_;
SrsFragment *fragment_;
public:
MockDvrSegmenter();
virtual void assemble();
virtual ~MockDvrSegmenter();
public:
virtual srs_error_t initialize(ISrsDvrPlan *p, ISrsRequest *r);
virtual SrsFragment *current();
virtual srs_error_t open();
virtual srs_error_t write_metadata(SrsMediaPacket *metadata);
virtual srs_error_t write_audio(SrsMediaPacket *shared_audio, SrsFormat *format);
virtual srs_error_t write_video(SrsMediaPacket *shared_video, SrsFormat *format);
virtual srs_error_t close();
};
// Mock ISrsOriginHub for testing SrsDvrSegmentPlan
class MockOriginHubForDvrSegmentPlan : public ISrsOriginHub
{
public:
int on_dvr_request_sh_count_;
srs_error_t on_dvr_request_sh_error_;
public:
MockOriginHubForDvrSegmentPlan();
virtual ~MockOriginHubForDvrSegmentPlan();
public:
virtual srs_error_t initialize(SrsSharedPtr<SrsLiveSource> s, ISrsRequest *r);
virtual void dispose();
virtual srs_error_t cycle();
virtual bool active();
virtual srs_utime_t cleanup_delay();
virtual srs_error_t on_meta_data(SrsMediaPacket *shared_metadata, SrsOnMetaDataPacket *packet);
virtual srs_error_t on_audio(SrsMediaPacket *shared_audio);
virtual srs_error_t on_video(SrsMediaPacket *shared_video, bool is_sequence_header);
virtual srs_error_t on_publish();
virtual void on_unpublish();
virtual srs_error_t on_dvr_request_sh();
};
#endif

View File

@ -2195,6 +2195,11 @@ SrsConfDirective *MockAppConfig::get_vhost_on_unpublish(std::string vhost)
return on_unpublish_directive_;
}
SrsConfDirective *MockAppConfig::get_vhost_on_dvr(std::string vhost)
{
return NULL;
}
bool MockAppConfig::get_rtc_nack_enabled(std::string vhost)
{
return rtc_nack_enabled_;

View File

@ -337,6 +337,7 @@ public:
virtual bool get_vhost_http_hooks_enabled(std::string vhost);
virtual SrsConfDirective *get_vhost_on_stop(std::string vhost);
virtual SrsConfDirective *get_vhost_on_unpublish(std::string vhost);
virtual SrsConfDirective *get_vhost_on_dvr(std::string vhost);
virtual bool get_rtc_nack_enabled(std::string vhost);
virtual bool get_rtc_nack_no_copy(std::string vhost);
virtual bool get_realtime_enabled(std::string vhost, bool is_rtc);
@ -391,6 +392,14 @@ public:
virtual bool get_atc_auto(std::string vhost);
virtual bool get_reduce_sequence_header(std::string vhost);
virtual bool get_parse_sps(std::string vhost);
// DVR methods
virtual std::string get_dvr_path(std::string vhost) { return "./[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv"; }
virtual std::string get_dvr_plan(std::string vhost) { return "session"; }
virtual bool get_dvr_enabled(std::string vhost) { return false; }
virtual SrsConfDirective *get_dvr_apply(std::string vhost) { return NULL; }
virtual srs_utime_t get_dvr_duration(std::string vhost) { return 30 * SRS_UTIME_SECONDS; }
virtual int get_dvr_time_jitter(std::string vhost) { return 0; }
virtual bool get_dvr_wait_keyframe(std::string vhost) { return true; }
virtual bool get_vhost_enabled(SrsConfDirective *conf) { return true; }
virtual bool get_vhost_http_remux_enabled(std::string vhost) { return false; }
virtual bool get_vhost_http_remux_enabled(SrsConfDirective *vhost) { return false; }

View File

@ -1457,12 +1457,16 @@ MockDvrForOriginHub::MockDvrForOriginHub()
on_video_count_ = 0;
}
void MockDvrForOriginHub::assemble()
{
}
MockDvrForOriginHub::~MockDvrForOriginHub()
{
srs_freep(initialize_error_);
}
srs_error_t MockDvrForOriginHub::initialize(SrsOriginHub *h, ISrsRequest *r)
srs_error_t MockDvrForOriginHub::initialize(ISrsOriginHub *h, ISrsRequest *r)
{
initialize_count_++;
return srs_error_copy(initialize_error_);
@ -3016,6 +3020,11 @@ void MockOriginHubForLiveSource::on_unpublish()
{
}
srs_error_t MockOriginHubForLiveSource::on_dvr_request_sh()
{
return srs_success;
}
MockAppFactoryForLiveSource::MockAppFactoryForLiveSource()
{
mock_hub_ = new MockOriginHubForLiveSource();

View File

@ -137,8 +137,9 @@ public:
public:
MockDvrForOriginHub();
virtual void assemble();
virtual ~MockDvrForOriginHub();
virtual srs_error_t initialize(SrsOriginHub *h, ISrsRequest *r);
virtual srs_error_t initialize(ISrsOriginHub *h, ISrsRequest *r);
virtual srs_error_t on_publish(ISrsRequest *r);
virtual void on_unpublish();
virtual srs_error_t on_meta_data(SrsMediaPacket *metadata);
@ -336,6 +337,7 @@ public:
virtual srs_error_t on_video(SrsMediaPacket *shared_video, bool is_sequence_header);
virtual srs_error_t on_publish();
virtual void on_unpublish();
virtual srs_error_t on_dvr_request_sh();
};
// Mock ISrsAppFactory for testing SrsLiveSource::initialize

View File

@ -150,6 +150,12 @@ void MockSrsFileWriter::close()
uf->close();
}
srs_error_t MockSrsFileWriter::set_iobuf_size(int size)
{
// Mock implementation - just return success
return srs_success;
}
bool MockSrsFileWriter::is_open()
{
return opened;

View File

@ -70,6 +70,7 @@ public:
public:
virtual srs_error_t open(std::string file);
virtual void close();
virtual srs_error_t set_iobuf_size(int size);
public:
virtual bool is_open();