1/*
2 * libjingle
3 * Copyright 2010 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/session/media/mediarecorder.h"
29
30#include <limits.h>
31
32#include <string>
33
34#include "talk/media/base/rtpdump.h"
35#include "webrtc/base/fileutils.h"
36#include "webrtc/base/logging.h"
37#include "webrtc/base/pathutils.h"
38
39
40namespace cricket {
41
42///////////////////////////////////////////////////////////////////////////
43// Implementation of RtpDumpSink.
44///////////////////////////////////////////////////////////////////////////
45RtpDumpSink::RtpDumpSink(rtc::StreamInterface* stream)
46    : max_size_(INT_MAX),
47      recording_(false),
48      packet_filter_(PF_NONE) {
49  stream_.reset(stream);
50}
51
52RtpDumpSink::~RtpDumpSink() {}
53
54void RtpDumpSink::SetMaxSize(size_t size) {
55  rtc::CritScope cs(&critical_section_);
56  max_size_ = size;
57}
58
59bool RtpDumpSink::Enable(bool enable) {
60  rtc::CritScope cs(&critical_section_);
61
62  recording_ = enable;
63
64  // Create a file and the RTP writer if we have not done yet.
65  if (recording_ && !writer_) {
66    if (!stream_) {
67      return false;
68    }
69    writer_.reset(new RtpDumpWriter(stream_.get()));
70    writer_->set_packet_filter(packet_filter_);
71  } else if (!recording_ && stream_) {
72    stream_->Flush();
73  }
74  return true;
75}
76
77void RtpDumpSink::OnPacket(const void* data, size_t size, bool rtcp) {
78  rtc::CritScope cs(&critical_section_);
79
80  if (recording_ && writer_) {
81    size_t current_size;
82    if (writer_->GetDumpSize(&current_size) &&
83        current_size + RtpDumpPacket::kHeaderLength + size <= max_size_) {
84      if (!rtcp) {
85        writer_->WriteRtpPacket(data, size);
86      } else {
87        // TODO(whyuan): Enable recording RTCP.
88      }
89    }
90  }
91}
92
93void RtpDumpSink::set_packet_filter(int filter) {
94  rtc::CritScope cs(&critical_section_);
95  packet_filter_ = filter;
96  if (writer_) {
97    writer_->set_packet_filter(packet_filter_);
98  }
99}
100
101void RtpDumpSink::Flush() {
102  rtc::CritScope cs(&critical_section_);
103  if (stream_) {
104    stream_->Flush();
105  }
106}
107
108///////////////////////////////////////////////////////////////////////////
109// Implementation of MediaRecorder.
110///////////////////////////////////////////////////////////////////////////
111MediaRecorder::MediaRecorder() {}
112
113MediaRecorder::~MediaRecorder() {
114  rtc::CritScope cs(&critical_section_);
115  std::map<BaseChannel*, SinkPair*>::iterator itr;
116  for (itr = sinks_.begin(); itr != sinks_.end(); ++itr) {
117    delete itr->second;
118  }
119}
120
121bool MediaRecorder::AddChannel(VoiceChannel* channel,
122                               rtc::StreamInterface* send_stream,
123                               rtc::StreamInterface* recv_stream,
124                               int filter) {
125  return InternalAddChannel(channel, false, send_stream, recv_stream,
126                            filter);
127}
128bool MediaRecorder::AddChannel(VideoChannel* channel,
129                               rtc::StreamInterface* send_stream,
130                               rtc::StreamInterface* recv_stream,
131                               int filter) {
132  return InternalAddChannel(channel, true, send_stream, recv_stream,
133                            filter);
134}
135
136bool MediaRecorder::InternalAddChannel(BaseChannel* channel,
137                                       bool video_channel,
138                                       rtc::StreamInterface* send_stream,
139                                       rtc::StreamInterface* recv_stream,
140                                       int filter) {
141  if (!channel) {
142    return false;
143  }
144
145  rtc::CritScope cs(&critical_section_);
146  if (sinks_.end() != sinks_.find(channel)) {
147    return false;  // The channel was added already.
148  }
149
150  SinkPair* sink_pair = new SinkPair;
151  sink_pair->video_channel = video_channel;
152  sink_pair->filter = filter;
153  sink_pair->send_sink.reset(new RtpDumpSink(send_stream));
154  sink_pair->send_sink->set_packet_filter(filter);
155  sink_pair->recv_sink.reset(new RtpDumpSink(recv_stream));
156  sink_pair->recv_sink->set_packet_filter(filter);
157  sinks_[channel] = sink_pair;
158
159  return true;
160}
161
162void MediaRecorder::RemoveChannel(BaseChannel* channel,
163                                  SinkType type) {
164  rtc::CritScope cs(&critical_section_);
165  std::map<BaseChannel*, SinkPair*>::iterator itr = sinks_.find(channel);
166  if (sinks_.end() != itr) {
167    channel->UnregisterSendSink(itr->second->send_sink.get(), type);
168    channel->UnregisterRecvSink(itr->second->recv_sink.get(), type);
169    delete itr->second;
170    sinks_.erase(itr);
171  }
172}
173
174bool MediaRecorder::EnableChannel(
175    BaseChannel* channel, bool enable_send, bool enable_recv,
176    SinkType type) {
177  rtc::CritScope cs(&critical_section_);
178  std::map<BaseChannel*, SinkPair*>::iterator itr = sinks_.find(channel);
179  if (sinks_.end() == itr) {
180    return false;
181  }
182
183  SinkPair* sink_pair = itr->second;
184  RtpDumpSink* sink = sink_pair->send_sink.get();
185  sink->Enable(enable_send);
186  if (enable_send) {
187    channel->RegisterSendSink(sink, &RtpDumpSink::OnPacket, type);
188  } else {
189    channel->UnregisterSendSink(sink, type);
190  }
191
192  sink = sink_pair->recv_sink.get();
193  sink->Enable(enable_recv);
194  if (enable_recv) {
195    channel->RegisterRecvSink(sink, &RtpDumpSink::OnPacket, type);
196  } else {
197    channel->UnregisterRecvSink(sink, type);
198  }
199
200  if (sink_pair->video_channel &&
201      (sink_pair->filter & PF_RTPPACKET) == PF_RTPPACKET) {
202    // Request a full intra frame.
203    VideoChannel* video_channel = static_cast<VideoChannel*>(channel);
204    if (enable_send) {
205      video_channel->SendIntraFrame();
206    }
207    if (enable_recv) {
208      video_channel->RequestIntraFrame();
209    }
210  }
211
212  return true;
213}
214
215void MediaRecorder::FlushSinks() {
216  rtc::CritScope cs(&critical_section_);
217  std::map<BaseChannel*, SinkPair*>::iterator itr;
218  for (itr = sinks_.begin(); itr != sinks_.end(); ++itr) {
219    itr->second->send_sink->Flush();
220    itr->second->recv_sink->Flush();
221  }
222}
223
224}  // namespace cricket
225