diff --git a/README.md b/README.md
index d83cca4da..aeae5b7d6 100755
--- a/README.md
+++ b/README.md
@@ -45,7 +45,8 @@ url: rtmp://127.0.0.1:1935/live/livestream
* nginx v1.5.0: 139524 lines
### History
-* v0.2, 2013-10-23, v0.2 released. 10125 lines.
+* v0.3, 2013-10-27, support cache last gop for client fast startup.
+* v0.2, 2013-10-25, v0.2 released. 10125 lines.
* v0.2, 2013-10-25, support flash publish.
* v0.2, 2013-10-25, support h264/avc codec by rtmp complex handshake(SrsComplexHandshake).
* v0.2, 2013-10-24, support time jitter detect and correct algorithm(SrsConsumer::jitter_correct).
diff --git a/trunk/src/core/srs_core_client.cpp b/trunk/src/core/srs_core_client.cpp
index a563f8afd..7cceb2367 100755
--- a/trunk/src/core/srs_core_client.cpp
+++ b/trunk/src/core/srs_core_client.cpp
@@ -145,7 +145,9 @@ int SrsClient::do_cycle()
return ret;
}
srs_info("start to publish stream %s success", req->stream.c_str());
- return streaming_publish(source, true);
+ ret = streaming_publish(source, true);
+ source->on_unpublish();
+ return ret;
}
case SrsClientFlashPublish: {
srs_verbose("flash start to publish stream %s.", req->stream.c_str());
@@ -155,7 +157,9 @@ int SrsClient::do_cycle()
return ret;
}
srs_info("flash start to publish stream %s success", req->stream.c_str());
- return streaming_publish(source, false);
+ ret = streaming_publish(source, false);
+ source->on_unpublish();
+ return ret;
}
default: {
ret = ERROR_SYSTEM_CLIENT_INVALID;
diff --git a/trunk/src/core/srs_core_codec.cpp b/trunk/src/core/srs_core_codec.cpp
index bd445d740..c2d0b0dfd 100755
--- a/trunk/src/core/srs_core_codec.cpp
+++ b/trunk/src/core/srs_core_codec.cpp
@@ -31,6 +31,35 @@ SrsCodec::~SrsCodec()
{
}
+bool SrsCodec::video_is_keyframe(int8_t* data, int size)
+{
+ // E.4.3.1 VIDEODATA
+ // Frame Type UB [4]
+ // Type of video frame. The following values are defined:
+ // 1 = key frame (for AVC, a seekable frame)
+ // 2 = inter frame (for AVC, a non-seekable frame)
+ // 3 = disposable inter frame (H.263 only)
+ // 4 = generated key frame (reserved for server use only)
+ // 5 = video info/command frame
+ //
+ // AVCPacketType IF CodecID == 7 UI8
+ // The following values are defined:
+ // 0 = AVC sequence header
+ // 1 = AVC NALU
+ // 2 = AVC end of sequence (lower level NALU sequence ender is
+ // not required or supported)
+
+ // 2bytes required.
+ if (size < 1) {
+ return false;
+ }
+
+ char frame_type = *(char*)data;
+ frame_type = (frame_type >> 4) & 0x0F;
+
+ return frame_type == 1;
+}
+
bool SrsCodec::video_is_sequence_header(int8_t* data, int size)
{
// E.4.3.1 VIDEODATA
diff --git a/trunk/src/core/srs_core_codec.hpp b/trunk/src/core/srs_core_codec.hpp
index 49e8cc2c8..ae73e6302 100755
--- a/trunk/src/core/srs_core_codec.hpp
+++ b/trunk/src/core/srs_core_codec.hpp
@@ -40,10 +40,26 @@ public:
SrsCodec();
virtual ~SrsCodec();
public:
+ /**
+ * only check the frame_type, not check the codec type.
+ */
+ virtual bool video_is_keyframe(int8_t* data, int size);
+ /**
+ * check codec h264, keyframe, sequence header
+ */
virtual bool video_is_sequence_header(int8_t* data, int size);
+ /**
+ * check codec aac, sequence header
+ */
virtual bool audio_is_sequence_header(int8_t* data, int size);
-private:
+ /**
+ * check codec h264.
+ */
virtual bool video_is_h264(int8_t* data, int size);
+private:
+ /**
+ * check codec aac.
+ */
virtual bool audio_is_aac(int8_t* data, int size);
};
diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp
index dada68bb8..22a4da2eb 100755
--- a/trunk/src/core/srs_core_source.cpp
+++ b/trunk/src/core/srs_core_source.cpp
@@ -64,6 +64,11 @@ SrsConsumer::~SrsConsumer()
source->on_consumer_destroy(this);
}
+int SrsConsumer::get_time()
+{
+ return (int)last_pkt_correct_time;
+}
+
int SrsConsumer::enqueue(SrsSharedPtrMessage* msg)
{
int ret = ERROR_SUCCESS;
@@ -144,10 +149,12 @@ int SrsConsumer::jitter_correct(SrsSharedPtrMessage* msg)
SrsSource::SrsSource(std::string _stream_url)
{
stream_url = _stream_url;
- cache_metadata = NULL;
- cache_sh_video = NULL;
- cache_sh_audio = NULL;
codec = new SrsCodec();
+
+ cache_metadata = cache_sh_video = cache_sh_audio = NULL;
+
+ cached_video_count = 0;
+ enable_gop_cache = true;
}
SrsSource::~SrsSource()
@@ -159,6 +166,8 @@ SrsSource::~SrsSource()
}
consumers.clear();
+ clear_gop_cache();
+
srs_freep(cache_metadata);
srs_freep(cache_sh_video);
srs_freep(cache_sh_audio);
@@ -246,8 +255,16 @@ int SrsSource::on_audio(SrsCommonMessage* audio)
if (codec->audio_is_sequence_header(msg->payload, msg->size)) {
srs_freep(cache_sh_audio);
cache_sh_audio = msg->copy();
+ return ret;
}
+ // cache the last gop packets
+ if ((ret = cache_last_gop(msg)) != ERROR_SUCCESS) {
+ srs_error("shrink gop cache failed. ret=%d", ret);
+ return ret;
+ }
+ srs_verbose("cache gop success.");
+
return ret;
}
@@ -282,7 +299,15 @@ int SrsSource::on_video(SrsCommonMessage* video)
if (codec->video_is_sequence_header(msg->payload, msg->size)) {
srs_freep(cache_sh_video);
cache_sh_video = msg->copy();
+ return ret;
}
+
+ // cache the last gop packets
+ if ((ret = cache_last_gop(msg)) != ERROR_SUCCESS) {
+ srs_error("shrink gop cache failed. ret=%d", ret);
+ return ret;
+ }
+ srs_verbose("cache gop success.");
return ret;
}
@@ -312,6 +337,16 @@ int SrsSource::on_video(SrsCommonMessage* video)
}
srs_info("dispatch audio sequence header success");
+ std::vector::iterator it;
+ for (it = gop_cache.begin(); it != gop_cache.end(); ++it) {
+ SrsSharedPtrMessage* msg = *it;
+ if ((ret = consumer->enqueue(msg->copy())) != ERROR_SUCCESS) {
+ srs_error("dispatch cached gop failed. ret=%d", ret);
+ return ret;
+ }
+ }
+ srs_trace("dispatch cached gop success. count=%d, duration=%d", (int)gop_cache.size(), consumer->get_time());
+
return ret;
}
@@ -325,3 +360,58 @@ void SrsSource::on_consumer_destroy(SrsConsumer* consumer)
srs_info("handle consumer destroy success.");
}
+void SrsSource::on_unpublish()
+{
+ clear_gop_cache();
+ srs_trace("clear cache when unpublish.");
+}
+
+int SrsSource::cache_last_gop(SrsSharedPtrMessage* msg)
+{
+ int ret = ERROR_SUCCESS;
+
+ if (!enable_gop_cache) {
+ srs_verbose("gop cache is disabled.");
+ return ret;
+ }
+
+ // got video, update the video count if acceptable
+ if (msg->header.is_video()) {
+ cached_video_count++;
+ }
+
+ // no acceptable video or pure audio, disable the cache.
+ if (cached_video_count == 0) {
+ srs_verbose("ignore any frame util got a h264 video frame.");
+ return ret;
+ }
+
+ // clear gop cache when got key frame
+ if (msg->header.is_video() && codec->video_is_keyframe(msg->payload, msg->size)) {
+ srs_info("clear gop cache when got keyframe. vcount=%d, count=%d",
+ cached_video_count, (int)gop_cache.size());
+
+ clear_gop_cache();
+
+ // curent msg is video frame, so we set to 1.
+ cached_video_count = 1;
+ }
+
+ // cache the frame.
+ gop_cache.push_back(msg->copy());
+
+ return ret;
+}
+
+void SrsSource::clear_gop_cache()
+{
+ std::vector::iterator it;
+ for (it = gop_cache.begin(); it != gop_cache.end(); ++it) {
+ SrsSharedPtrMessage* msg = *it;
+ srs_freep(msg);
+ }
+ gop_cache.clear();
+
+ cached_video_count = 0;
+}
+
diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp
index dedcaa5f1..b2737aeec 100755
--- a/trunk/src/core/srs_core_source.hpp
+++ b/trunk/src/core/srs_core_source.hpp
@@ -54,6 +54,10 @@ public:
SrsConsumer(SrsSource* _source);
virtual ~SrsConsumer();
public:
+ /**
+ * get current client time, the last packet time.
+ */
+ virtual int get_time();
/**
* enqueue an shared ptr message.
*/
@@ -92,6 +96,22 @@ private:
SrsCodec* codec;
std::string stream_url;
std::vector consumers;
+// gop cache for client fast startup.
+private:
+ /**
+ * if disabled the gop cache,
+ * the client will wait for the next keyframe for h264,
+ * and will be black-screen.
+ */
+ bool enable_gop_cache;
+ /**
+ * the video frame count, avoid cache for pure audio stream.
+ */
+ int cached_video_count;
+ /**
+ * cached gop.
+ */
+ std::vector gop_cache;
private:
SrsSharedPtrMessage* cache_metadata;
// the cached video sequence header.
@@ -108,6 +128,15 @@ public:
public:
virtual int create_consumer(SrsConsumer*& consumer);
virtual void on_consumer_destroy(SrsConsumer* consumer);
+ virtual void on_unpublish();
+private:
+ /**
+ * only for h264 codec
+ * 1. cache the gop when got h264 video packet.
+ * 2. clear gop when got keyframe.
+ */
+ virtual int cache_last_gop(SrsSharedPtrMessage* msg);
+ virtual void clear_gop_cache();
};
#endif
\ No newline at end of file