// libjingle // Copyright 2010 Google Inc. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // 3. The name of the author may not be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "talk/media/base/fakemediaengine.h" #include "talk/media/base/rtpdump.h" #include "talk/media/base/testutils.h" #include "webrtc/p2p/base/fakesession.h" #include "talk/session/media/channel.h" #include "talk/session/media/mediarecorder.h" #include "webrtc/base/bytebuffer.h" #include "webrtc/base/fileutils.h" #include "webrtc/base/gunit.h" #include "webrtc/base/pathutils.h" #include "webrtc/base/thread.h" namespace cricket { rtc::StreamInterface* Open(const std::string& path) { return rtc::Filesystem::OpenFile( rtc::Pathname(path), "wb"); } ///////////////////////////////////////////////////////////////////////// // Test RtpDumpSink ///////////////////////////////////////////////////////////////////////// class RtpDumpSinkTest : public testing::Test { public: virtual void SetUp() { EXPECT_TRUE(rtc::Filesystem::GetTemporaryFolder(path_, true, NULL)); path_.SetFilename("sink-test.rtpdump"); sink_.reset(new RtpDumpSink(Open(path_.pathname()))); for (int i = 0; i < ARRAY_SIZE(rtp_buf_); ++i) { RtpTestUtility::kTestRawRtpPackets[i].WriteToByteBuffer( RtpTestUtility::kDefaultSsrc, &rtp_buf_[i]); } } virtual void TearDown() { stream_.reset(); EXPECT_TRUE(rtc::Filesystem::DeleteFile(path_)); } protected: void OnRtpPacket(const RawRtpPacket& raw) { rtc::ByteBuffer buf; raw.WriteToByteBuffer(RtpTestUtility::kDefaultSsrc, &buf); sink_->OnPacket(buf.Data(), buf.Length(), false); } rtc::StreamResult ReadPacket(RtpDumpPacket* packet) { if (!stream_.get()) { sink_.reset(); // This will close the file. So we can read it. stream_.reset(rtc::Filesystem::OpenFile(path_, "rb")); reader_.reset(new RtpDumpReader(stream_.get())); } return reader_->ReadPacket(packet); } rtc::Pathname path_; rtc::scoped_ptr sink_; rtc::ByteBuffer rtp_buf_[3]; rtc::scoped_ptr stream_; rtc::scoped_ptr reader_; }; TEST_F(RtpDumpSinkTest, TestRtpDumpSink) { // By default, the sink is disabled. The 1st packet is not written. EXPECT_FALSE(sink_->IsEnabled()); sink_->set_packet_filter(PF_ALL); OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]); // Enable the sink. The 2nd packet is written. EXPECT_TRUE(sink_->Enable(true)); EXPECT_TRUE(sink_->IsEnabled()); EXPECT_TRUE(rtc::Filesystem::IsFile(path_.pathname())); OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]); // Disable the sink. The 3rd packet is not written. EXPECT_TRUE(sink_->Enable(false)); EXPECT_FALSE(sink_->IsEnabled()); OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]); // Read the recorded file and verify it contains only the 2nd packet. RtpDumpPacket packet; EXPECT_EQ(rtc::SR_SUCCESS, ReadPacket(&packet)); EXPECT_TRUE(RtpTestUtility::VerifyPacket( &packet, &RtpTestUtility::kTestRawRtpPackets[1], false)); EXPECT_EQ(rtc::SR_EOS, ReadPacket(&packet)); } TEST_F(RtpDumpSinkTest, TestRtpDumpSinkMaxSize) { EXPECT_TRUE(sink_->Enable(true)); sink_->set_packet_filter(PF_ALL); sink_->SetMaxSize(strlen(RtpDumpFileHeader::kFirstLine) + RtpDumpFileHeader::kHeaderLength + RtpDumpPacket::kHeaderLength + RtpTestUtility::kTestRawRtpPackets[0].size()); OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]); // Exceed the limit size: the 2nd and 3rd packets are not written. OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]); OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]); // Read the recorded file and verify that it contains only the first packet. RtpDumpPacket packet; EXPECT_EQ(rtc::SR_SUCCESS, ReadPacket(&packet)); EXPECT_TRUE(RtpTestUtility::VerifyPacket( &packet, &RtpTestUtility::kTestRawRtpPackets[0], false)); EXPECT_EQ(rtc::SR_EOS, ReadPacket(&packet)); } TEST_F(RtpDumpSinkTest, TestRtpDumpSinkFilter) { // The default filter is PF_NONE. EXPECT_EQ(PF_NONE, sink_->packet_filter()); // Set to PF_RTPHEADER before enable. sink_->set_packet_filter(PF_RTPHEADER); EXPECT_EQ(PF_RTPHEADER, sink_->packet_filter()); EXPECT_TRUE(sink_->Enable(true)); // We dump only the header of the first packet. OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]); // Set the filter to PF_RTPPACKET. We dump all the second packet. sink_->set_packet_filter(PF_RTPPACKET); EXPECT_EQ(PF_RTPPACKET, sink_->packet_filter()); OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]); // Set the filter to PF_NONE. We do not dump the third packet. sink_->set_packet_filter(PF_NONE); EXPECT_EQ(PF_NONE, sink_->packet_filter()); OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]); // Read the recorded file and verify the header of the first packet and // the whole packet for the second packet. RtpDumpPacket packet; EXPECT_EQ(rtc::SR_SUCCESS, ReadPacket(&packet)); EXPECT_TRUE(RtpTestUtility::VerifyPacket( &packet, &RtpTestUtility::kTestRawRtpPackets[0], true)); EXPECT_EQ(rtc::SR_SUCCESS, ReadPacket(&packet)); EXPECT_TRUE(RtpTestUtility::VerifyPacket( &packet, &RtpTestUtility::kTestRawRtpPackets[1], false)); EXPECT_EQ(rtc::SR_EOS, ReadPacket(&packet)); } ///////////////////////////////////////////////////////////////////////// // Test MediaRecorder ///////////////////////////////////////////////////////////////////////// void TestMediaRecorder(BaseChannel* channel, FakeVideoMediaChannel* video_media_channel, int filter) { // Create media recorder. rtc::scoped_ptr recorder(new MediaRecorder); // Fail to EnableChannel before AddChannel. EXPECT_FALSE(recorder->EnableChannel(channel, true, true, SINK_PRE_CRYPTO)); EXPECT_FALSE(channel->HasSendSinks(SINK_PRE_CRYPTO)); EXPECT_FALSE(channel->HasRecvSinks(SINK_PRE_CRYPTO)); EXPECT_FALSE(channel->HasSendSinks(SINK_POST_CRYPTO)); EXPECT_FALSE(channel->HasRecvSinks(SINK_POST_CRYPTO)); // Add the channel to the recorder. rtc::Pathname path; EXPECT_TRUE(rtc::Filesystem::GetTemporaryFolder(path, true, NULL)); path.SetFilename("send.rtpdump"); std::string send_file = path.pathname(); path.SetFilename("recv.rtpdump"); std::string recv_file = path.pathname(); if (video_media_channel) { EXPECT_TRUE(recorder->AddChannel(static_cast(channel), Open(send_file), Open(recv_file), filter)); } else { EXPECT_TRUE(recorder->AddChannel(static_cast(channel), Open(send_file), Open(recv_file), filter)); } // Enable recording only the sent media. EXPECT_TRUE(recorder->EnableChannel(channel, true, false, SINK_PRE_CRYPTO)); EXPECT_TRUE(channel->HasSendSinks(SINK_PRE_CRYPTO)); EXPECT_FALSE(channel->HasRecvSinks(SINK_POST_CRYPTO)); EXPECT_FALSE(channel->HasSendSinks(SINK_POST_CRYPTO)); EXPECT_FALSE(channel->HasRecvSinks(SINK_POST_CRYPTO)); if (video_media_channel) { EXPECT_TRUE_WAIT(video_media_channel->sent_intra_frame(), 100); } // Enable recording only the received meida. EXPECT_TRUE(recorder->EnableChannel(channel, false, true, SINK_PRE_CRYPTO)); EXPECT_FALSE(channel->HasSendSinks(SINK_PRE_CRYPTO)); EXPECT_TRUE(channel->HasRecvSinks(SINK_PRE_CRYPTO)); if (video_media_channel) { EXPECT_TRUE(video_media_channel->requested_intra_frame()); } // Enable recording both the sent and the received video. EXPECT_TRUE(recorder->EnableChannel(channel, true, true, SINK_PRE_CRYPTO)); EXPECT_TRUE(channel->HasSendSinks(SINK_PRE_CRYPTO)); EXPECT_TRUE(channel->HasRecvSinks(SINK_PRE_CRYPTO)); // Enable recording only headers. if (video_media_channel) { video_media_channel->set_sent_intra_frame(false); video_media_channel->set_requested_intra_frame(false); } EXPECT_TRUE(recorder->EnableChannel(channel, true, true, SINK_PRE_CRYPTO)); EXPECT_TRUE(channel->HasSendSinks(SINK_PRE_CRYPTO)); EXPECT_TRUE(channel->HasRecvSinks(SINK_PRE_CRYPTO)); if (video_media_channel) { if ((filter & PF_RTPPACKET) == PF_RTPPACKET) { // If record the whole RTP packet, trigers FIR. EXPECT_TRUE(video_media_channel->requested_intra_frame()); EXPECT_TRUE(video_media_channel->sent_intra_frame()); } else { // If record only the RTP header, does not triger FIR. EXPECT_FALSE(video_media_channel->requested_intra_frame()); EXPECT_FALSE(video_media_channel->sent_intra_frame()); } } // Remove the voice channel from the recorder. recorder->RemoveChannel(channel, SINK_PRE_CRYPTO); EXPECT_FALSE(channel->HasSendSinks(SINK_PRE_CRYPTO)); EXPECT_FALSE(channel->HasRecvSinks(SINK_PRE_CRYPTO)); // Delete all files. recorder.reset(); EXPECT_TRUE(rtc::Filesystem::DeleteFile(send_file)); EXPECT_TRUE(rtc::Filesystem::DeleteFile(recv_file)); } // Fisrt start recording header and then start recording media. Verify that // differnt files are created for header and media. void TestRecordHeaderAndMedia(BaseChannel* channel, FakeVideoMediaChannel* video_media_channel) { // Create RTP header recorder. rtc::scoped_ptr header_recorder(new MediaRecorder); rtc::Pathname path; EXPECT_TRUE(rtc::Filesystem::GetTemporaryFolder(path, true, NULL)); path.SetFilename("send-header.rtpdump"); std::string send_header_file = path.pathname(); path.SetFilename("recv-header.rtpdump"); std::string recv_header_file = path.pathname(); if (video_media_channel) { EXPECT_TRUE(header_recorder->AddChannel( static_cast(channel), Open(send_header_file), Open(recv_header_file), PF_RTPHEADER)); } else { EXPECT_TRUE(header_recorder->AddChannel( static_cast(channel), Open(send_header_file), Open(recv_header_file), PF_RTPHEADER)); } // Enable recording both sent and received. EXPECT_TRUE( header_recorder->EnableChannel(channel, true, true, SINK_POST_CRYPTO)); EXPECT_TRUE(channel->HasSendSinks(SINK_POST_CRYPTO)); EXPECT_TRUE(channel->HasRecvSinks(SINK_POST_CRYPTO)); EXPECT_FALSE(channel->HasSendSinks(SINK_PRE_CRYPTO)); EXPECT_FALSE(channel->HasRecvSinks(SINK_PRE_CRYPTO)); if (video_media_channel) { EXPECT_FALSE(video_media_channel->sent_intra_frame()); EXPECT_FALSE(video_media_channel->requested_intra_frame()); } // Verify that header files are created. EXPECT_TRUE(rtc::Filesystem::IsFile(send_header_file)); EXPECT_TRUE(rtc::Filesystem::IsFile(recv_header_file)); // Create RTP header recorder. rtc::scoped_ptr recorder(new MediaRecorder); path.SetFilename("send.rtpdump"); std::string send_file = path.pathname(); path.SetFilename("recv.rtpdump"); std::string recv_file = path.pathname(); if (video_media_channel) { EXPECT_TRUE(recorder->AddChannel( static_cast(channel), Open(send_file), Open(recv_file), PF_RTPPACKET)); } else { EXPECT_TRUE(recorder->AddChannel( static_cast(channel), Open(send_file), Open(recv_file), PF_RTPPACKET)); } // Enable recording both sent and received. EXPECT_TRUE(recorder->EnableChannel(channel, true, true, SINK_PRE_CRYPTO)); EXPECT_TRUE(channel->HasSendSinks(SINK_POST_CRYPTO)); EXPECT_TRUE(channel->HasRecvSinks(SINK_POST_CRYPTO)); EXPECT_TRUE(channel->HasSendSinks(SINK_PRE_CRYPTO)); EXPECT_TRUE(channel->HasRecvSinks(SINK_PRE_CRYPTO)); if (video_media_channel) { EXPECT_TRUE_WAIT(video_media_channel->sent_intra_frame(), 100); EXPECT_TRUE(video_media_channel->requested_intra_frame()); } // Verify that media files are created. EXPECT_TRUE(rtc::Filesystem::IsFile(send_file)); EXPECT_TRUE(rtc::Filesystem::IsFile(recv_file)); // Delete all files. header_recorder.reset(); recorder.reset(); EXPECT_TRUE(rtc::Filesystem::DeleteFile(send_header_file)); EXPECT_TRUE(rtc::Filesystem::DeleteFile(recv_header_file)); EXPECT_TRUE(rtc::Filesystem::DeleteFile(send_file)); EXPECT_TRUE(rtc::Filesystem::DeleteFile(recv_file)); } TEST(MediaRecorderTest, TestMediaRecorderVoiceChannel) { // Create the voice channel. FakeSession session(true); FakeMediaEngine media_engine; VoiceChannel channel(rtc::Thread::Current(), &media_engine, new FakeVoiceMediaChannel(NULL), &session, "", false); EXPECT_TRUE(channel.Init()); TestMediaRecorder(&channel, NULL, PF_RTPPACKET); TestMediaRecorder(&channel, NULL, PF_RTPHEADER); TestRecordHeaderAndMedia(&channel, NULL); } TEST(MediaRecorderTest, TestMediaRecorderVideoChannel) { // Create the video channel. FakeSession session(true); FakeMediaEngine media_engine; FakeVideoMediaChannel* media_channel = new FakeVideoMediaChannel(NULL); VideoChannel channel(rtc::Thread::Current(), &media_engine, media_channel, &session, "", false, NULL); EXPECT_TRUE(channel.Init()); TestMediaRecorder(&channel, media_channel, PF_RTPPACKET); TestMediaRecorder(&channel, media_channel, PF_RTPHEADER); TestRecordHeaderAndMedia(&channel, media_channel); } } // namespace cricket