Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(439)

Side by Side Diff: media/mpeg2/mpeg2ts_stream_parser.cc

Issue 23566013: Mpeg2 TS stream parser for media source. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/mpeg2/mpeg2ts_stream_parser.h"
6
7 #include "base/bind.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "media/base/audio_decoder_config.h"
10 #include "media/base/stream_parser_buffer.h"
11 #include "media/base/video_decoder_config.h"
12 #include "media/mpeg2/es_parser.h"
13 #include "media/mpeg2/es_parser_adts.h"
14 #include "media/mpeg2/es_parser_h264.h"
15 #include "media/mpeg2/mpeg2ts_common.h"
16 #include "media/mpeg2/mpeg2ts_pat.h"
17 #include "media/mpeg2/mpeg2ts_pes.h"
18 #include "media/mpeg2/mpeg2ts_pmt.h"
19 #include "media/mpeg2/mpeg2ts_section_parser.h"
20 #include "media/mpeg2/ts_packet.h"
21
22 namespace {
23
24 enum StreamType {
25 kStreamTypeMpeg1Audio = 0x3,
26 kStreamTypeAAC = 0xf,
27 kStreamTypeAVC = 0x1b,
28 };
29
30 }
31
32 namespace media {
33 namespace mpeg2ts {
34
35 class PidState {
36 public:
37 int pid;
38 int continuity_counter;
39 scoped_ptr<Mpeg2TsSectionParser> section_parser;
40 };
41
42 Mpeg2TsStreamParser::Mpeg2TsStreamParser()
43 : pids_deleter_(&pids_),
44 selected_pmt_pid_(-1),
45 selected_audio_pid_(-1),
46 selected_video_pid_(-1),
47 is_initialized_(false),
48 segment_started_(false) {
49 }
50
51 Mpeg2TsStreamParser::~Mpeg2TsStreamParser() {
52 }
53
54 void Mpeg2TsStreamParser::Init(
55 const InitCB& init_cb,
56 const NewConfigCB& config_cb,
57 const NewBuffersCB& new_buffers_cb,
58 const NewTextBuffersCB& text_cb,
59 const NeedKeyCB& need_key_cb,
60 const AddTextTrackCB& add_text_track_cb,
61 const NewMediaSegmentCB& new_segment_cb,
62 const base::Closure& end_of_segment_cb,
63 const LogCB& log_cb) {
64 DCHECK(init_cb_.is_null());
65 DCHECK(!init_cb.is_null());
66 DCHECK(!config_cb.is_null());
67 DCHECK(!new_buffers_cb.is_null());
68 DCHECK(!need_key_cb.is_null());
69 DCHECK(!end_of_segment_cb.is_null());
70
71 init_cb_ = init_cb;
72 config_cb_ = config_cb;
73 new_buffers_cb_ = new_buffers_cb;
74 need_key_cb_ = need_key_cb;
75 new_segment_cb_ = new_segment_cb;
76 end_of_segment_cb_ = end_of_segment_cb;
77 log_cb_ = log_cb;
78 }
79
80 void Mpeg2TsStreamParser::Flush() {
81 // Flush the buffers and reset the pids.
82 for (std::map<int, PidState*>::iterator it = pids_.begin();
83 it != pids_.end(); ++it) {
84 LOG(INFO) << "Flushing PID: " << it->first;
85 PidState* pid_state = it->second;
86 pid_state->section_parser->Flush();
87 delete pid_state;
88 }
89 pids_.clear();
90 EmitRemainingBuffers();
91
92 // End of the segment.
93 end_of_segment_cb_.Run();
94 segment_started_ = false;
95
96 // Remove any bytes left in the TS buffer.
97 // (i.e. any partial TS packet => less than 188 bytes).
98 ts_buffer_.clear();
99
100 // Remove the programs and the corresponding PIDs.
101 programs_.clear();
102
103 // Reset the selected PIDs.
104 selected_pmt_pid_ = -1;
105 selected_audio_pid_ = -1;
106 selected_video_pid_ = -1;
107
108 // Reset the audio and video configs.
109 audio_config_.reset();
110 video_config_.reset();
111
112 // TODO(damienv): What to do with the remaining audio/video buffer queues ?
113 // Note: there should not be any.
114 LOG_IF(WARNING, !audio_buffer_queue_.empty())
115 << "Flush: audio buffer queue not empty";
116 LOG_IF(WARNING, !video_buffer_queue_.empty())
117 << "Flush: video buffer queue not empty";
118 audio_buffer_queue_.clear();
119 video_buffer_queue_.clear();
120 }
121
122 bool Mpeg2TsStreamParser::Parse(const uint8* buf, int size) {
123 LOG(INFO) << "Mpeg2TsStreamParser::Parse size=" << size;
124
125 // Add the data to the parser state.
126 int old_size = ts_buffer_.size();
127 ts_buffer_.resize(old_size + size);
128 memcpy(&ts_buffer_[old_size], buf, size);
129
130 int remaining_size = ts_buffer_.size();
131 int pos = 0;
132 while (remaining_size >= TsPacket::kPacketSize) {
133 // Synchronization.
134 int skipped_bytes = TsPacket::Sync(&ts_buffer_[pos], remaining_size);
135 if (skipped_bytes > 0) {
136 LOG(WARNING) << "Packet not aligned on a TS syncword:"
137 << " skipped_bytes=" << skipped_bytes;
138 pos += skipped_bytes;
139 remaining_size -= skipped_bytes;
140 continue;
141 }
142
143 // Parse the TS header.
144 scoped_ptr<TsPacket> ts_packet(
145 TsPacket::Parse(&ts_buffer_[pos], remaining_size));
146 if (!ts_packet.get()) {
147 LOG(WARNING) << "Error: invalid TS packet";
148 pos += 1;
149 remaining_size -= 1;
150 continue;
151 }
152
153 VLOG(LOG_LEVEL_TS)
154 << "Processing PID=" << ts_packet->pid()
155 << " start_unit=" << ts_packet->payload_unit_start_indicator();
156
157 // Parse the section.
158 std::map<int, PidState*>::iterator it =
159 pids_.find(ts_packet->pid());
160 if (it == pids_.end() && ts_packet->pid() == 0) {
acolwell GONE FROM CHROMIUM 2013/08/29 20:44:24 nit: Use Mpeg2TsSectionParser::kPidPat here. You m
damienv1 2013/09/04 01:37:14 Done.
161 // Create the PAT state here if needed.
162 LOG(INFO) << "Create a new PAT parser";
163 PidState* pat_state = new PidState;
164 pat_state->section_parser.reset(
165 new Mpeg2TsPatParser(
166 base::Bind(&Mpeg2TsStreamParser::RegisterPmt,
167 base::Unretained(this))));
168 it = pids_.insert(
169 std::pair<int, PidState*>(ts_packet->pid(), pat_state)).first;
170 }
171
172 LOG_IF(WARNING, it == pids_.end()) << "Ignoring TS packet for pid: "
173 << ts_packet->pid();
174 if (it != pids_.end()) {
175 it->second->section_parser->Parse(
176 ts_packet->payload_unit_start_indicator(),
177 &ts_buffer_[pos+ts_packet->GetPayloadOffset()],
178 ts_packet->GetPayloadSize());
179 }
180
181 // Go to the next packet.
182 pos += TsPacket::kPacketSize;
183 remaining_size -= TsPacket::kPacketSize;
184 }
185
186 // Check whether we are on a TS packet boundary.
187 if (remaining_size > 0) {
188 memmove(&ts_buffer_[0], &ts_buffer_[pos], remaining_size);
189 ts_buffer_.resize(remaining_size);
190 } else {
191 ts_buffer_.resize(0);
192 }
193
194 EmitRemainingBuffers();
195
196 return true;
197 }
198
199 void Mpeg2TsStreamParser::RegisterPmt(int program_number, int pmt_pid) {
200 LOG(INFO) << "RegisterPmt:"
201 << " program_number=" << program_number
202 << " pmt_pid=" << pmt_pid;
203 std::map<int, PidState*>::iterator it = pids_.find(pmt_pid);
204 if (it != pids_.end()) {
205 return;
206 }
207
208 if (selected_pmt_pid_ < 0) {
209 selected_pmt_pid_ = pmt_pid;
210 }
211 LOG_IF(WARNING, selected_pmt_pid_ != pmt_pid)
212 << "More than one program is defined";
213
214 // Create the PMT state here if needed.
215 LOG(INFO) << "Create a new PMT parser";
216 PidState* pmt_state = new PidState;
217 pmt_state->section_parser.reset(
218 new Mpeg2TsPmtParser(
219 base::Bind(&Mpeg2TsStreamParser::RegisterPes,
220 base::Unretained(this), pmt_pid)));
221 pids_.insert(std::pair<int, PidState*>(pmt_pid, pmt_state));
222 }
223
224 void Mpeg2TsStreamParser::RegisterPes(int pmt_pid,
225 int pes_pid,
226 int stream_type) {
227 // TODO(damienv): check there is no mismatch if the entry already exists.
228 LOG(INFO) << "RegisterPes:"
229 << " pes_pid=" << pes_pid
230 << " stream_type=" << std::hex << stream_type << std::dec;
231 std::map<int, PidState*>::iterator it = pids_.find(pes_pid);
232 if (it != pids_.end()) {
233 return;
234 }
235
236 // TODO(damienv): add other formats if needed.
237 bool is_audio = (stream_type == kStreamTypeAAC);
238 bool is_video = (stream_type == kStreamTypeAVC);
239
240 // Update the active tracks.
241 // Select the audio/video tracks with the lowest PID.
242 if (pmt_pid == selected_pmt_pid_) {
243 if (is_audio &&
244 (selected_audio_pid_ < 0 || pes_pid < selected_audio_pid_)) {
245 selected_audio_pid_ = pes_pid;
246 }
247 if (is_video &&
248 (selected_video_pid_ < 0 || pes_pid < selected_video_pid_)) {
249 selected_video_pid_ = pes_pid;
250 }
251 }
252
253 // Create a stream parser corresponding to the stream type.
254 scoped_ptr<EsParser> es_parser;
255 if (stream_type == kStreamTypeAVC) {
256 es_parser.reset(
257 new EsParserH264(
258 base::Bind(&Mpeg2TsStreamParser::OnVideoConfigChanged,
259 base::Unretained(this),
260 pes_pid),
261 base::Bind(&Mpeg2TsStreamParser::OnEmitVideoBuffer,
262 base::Unretained(this),
263 pes_pid)));
264 } else if (stream_type == kStreamTypeAAC) {
265 es_parser.reset(
266 new EsParserAdts(
267 base::Bind(&Mpeg2TsStreamParser::OnAudioConfigChanged,
268 base::Unretained(this),
269 pes_pid),
270 base::Bind(&Mpeg2TsStreamParser::OnEmitAudioBuffer,
271 base::Unretained(this),
272 pes_pid)));
273 }
274
275 // Create the PES state here.
276 LOG(INFO) << "Create a new PES state";
277 PidState* pes_state = new PidState;
278 pes_state->section_parser.reset(
279 new Mpeg2TsPesParser(es_parser.release()));
280 pids_.insert(std::pair<int, PidState*>(pes_pid, pes_state));
281 }
282
283 void Mpeg2TsStreamParser::OnVideoConfigChanged(
284 int pes_pid,
285 const VideoDecoderConfig& video_decoder_config) {
286 DCHECK_GT(selected_video_pid_, 0);
287 LOG(INFO) << "OnVideoConfigChanged for pid=" << pes_pid;
288 if (pes_pid != selected_video_pid_) {
289 return;
290 }
291
292 video_config_.reset(new VideoDecoderConfig());
293 *video_config_ = video_decoder_config;
294
295 OnAudioVideoConfigChanged();
296 }
297
298 void Mpeg2TsStreamParser::OnAudioConfigChanged(
299 int pes_pid,
300 const AudioDecoderConfig& audio_decoder_config) {
301 LOG(INFO) << "OnAudioConfigChanged";
302 if (pes_pid != selected_audio_pid_) {
303 return;
304 }
305
306 audio_config_.reset(new AudioDecoderConfig());
307 *audio_config_ = audio_decoder_config;
308
309 OnAudioVideoConfigChanged();
310 }
311
312 void Mpeg2TsStreamParser::OnAudioVideoConfigChanged() {
313 if (selected_audio_pid_ > 0 && !audio_config_) {
314 // Need to get the audio config as well before going any further.
315 return;
316 }
317 if (selected_video_pid_ > 0 && !video_config_) {
318 // Need to get the video config as well before going any further.
319 return;
320 }
321
322 // Emit pending buffers only if already initialized.
323 // Buffers need to be emitted since these buffers do not necesseraly
324 // have the same config.
325 if (is_initialized_) {
326 // TODO(damienv): at initialization time, although unlikely,
327 // some situations like:
328 // VConfig0 VBuffer0 VBuffer1 VConfig1 VBuffer0 AConfig0 ABuffer0
329 // cannot be handled properly since video buffers will be assigned the same
330 // video config.
331 EmitRemainingBuffers();
332 }
333
334 if (selected_audio_pid_ > 0 && selected_video_pid_ > 0) {
335 // Streams with both audio and video.
336 config_cb_.Run(*audio_config_, *video_config_);
337 } else if (selected_video_pid_ > 0) {
338 // Video stream only.
339 AudioDecoderConfig audio_config;
340 config_cb_.Run(audio_config, *video_config_);
341 } else if (selected_audio_pid_ > 0) {
342 // Audio stream only.
343 VideoDecoderConfig video_config;
344 config_cb_.Run(*audio_config_, video_config);
345 } else {
346 // At least one audio or video config should be set.
347 NOTREACHED();
348 }
349
350 if (!is_initialized_) {
351 // For Mpeg2 TS we don't know the stream duration.
352 LOG(INFO) << "Mpeg2TS stream parser initialization done";
353 init_cb_.Run(true, base::TimeDelta());
354 is_initialized_ = true;
355 }
356 }
357
358 void Mpeg2TsStreamParser::OnEmitAudioBuffer(
359 int pes_pid,
360 scoped_refptr<StreamParserBuffer> stream_parser_buffer) {
361 // Since ChunkDemuxer currently handle only one audio and one video,
362 // the PID filter must be done inside the TS parser.
363 // Eventually, the PID filter will be done on the JS application side
364 // (using the HTML5 video element track selection).
365 if (pes_pid != selected_audio_pid_) {
366 return;
367 }
368
369 VLOG(LOG_LEVEL_ES)
370 << "OnEmitAudioBuffer: "
371 << " size="
372 << stream_parser_buffer->data_size()
373 << " dts="
374 << stream_parser_buffer->GetDecodeTimestamp().InMilliseconds()
375 << " pts="
376 << stream_parser_buffer->timestamp().InMilliseconds();
377 stream_parser_buffer->set_timestamp(
378 stream_parser_buffer->timestamp() - time_offset_);
379 stream_parser_buffer->SetDecodeTimestamp(
380 stream_parser_buffer->GetDecodeTimestamp() - time_offset_);
381 audio_buffer_queue_.push_back(stream_parser_buffer);
382 }
383
384 void Mpeg2TsStreamParser::OnEmitVideoBuffer(
385 int pes_pid,
386 scoped_refptr<StreamParserBuffer> stream_parser_buffer) {
387 // Since ChunkDemuxer currently handle only one audio and one video,
388 // the PID filter must be done inside the TS parser.
389 // Eventually, the PID filter will be done on the JS application side
390 // (using the HTML5 video element track selection).
391 if (pes_pid != selected_video_pid_) {
392 return;
393 }
394
395 VLOG(LOG_LEVEL_ES)
396 << "OnEmitVideoBuffer"
397 << " size="
398 << stream_parser_buffer->data_size()
399 << " dts="
400 << stream_parser_buffer->GetDecodeTimestamp().InMilliseconds()
401 << " pts="
402 << stream_parser_buffer->timestamp().InMilliseconds()
403 << " IsKeyframe="
404 << stream_parser_buffer->IsKeyframe();
405 stream_parser_buffer->set_timestamp(
406 stream_parser_buffer->timestamp() - time_offset_);
407 stream_parser_buffer->SetDecodeTimestamp(
408 stream_parser_buffer->GetDecodeTimestamp() - time_offset_);
409 video_buffer_queue_.push_back(stream_parser_buffer);
410 }
411
412 void Mpeg2TsStreamParser::EmitRemainingBuffers() {
413 VLOG(LOG_LEVEL_ES) << "Mpeg2TsStreamParser::EmitRemainingBuffers";
414
415 // Segment cannot start with non key frames
416 // so replace non key frames with the 1st IDR.
417 // TODO(damienv): Non key frames could be replaced with a stuffing NAL
418 // as well, the most important is to avoid any gap between frames
419 // as MSE does not allow sparse video/audio buffers.
420 if (!segment_started_) {
421 StreamParser::BufferQueue::iterator it = video_buffer_queue_.begin();
422 for ( ; it != video_buffer_queue_.end(); ++it) {
423 if ((*it)->IsKeyframe()) {
424 break;
425 }
426 }
427 if (it != video_buffer_queue_.end()) {
428 StreamParser::BufferQueue::iterator it2 = video_buffer_queue_.begin();
429 while (!(*it2)->IsKeyframe()) {
430 base::TimeDelta dts = (*it2)->GetDecodeTimestamp();
431 base::TimeDelta pts = (*it2)->timestamp();
432 scoped_refptr<StreamParserBuffer> stream_parser_buffer =
433 StreamParserBuffer::CopyFrom(
434 (*it)->data(), (*it)->data_size(), true);
435 stream_parser_buffer->set_timestamp(pts);
436 stream_parser_buffer->SetDecodeTimestamp(dts);
437 LOG(WARNING) << "Replacing frame with an IDR @ pts="
438 << pts.InMilliseconds();
439 *it2 = stream_parser_buffer;
440 ++it2;
441 }
442 } else {
443 // TODO(damienv): is there a better way to handle this situation ?
444 LOG(WARNING) << "Only non key frames in the buffer queue";
445 video_buffer_queue_.clear();
446 }
447 }
448
449 // Possibly start a segment if not done yet.
450 StartSegmentIfNeeded();
451
452 // Finally, add the video and audio buffers.
453 if (!video_buffer_queue_.empty() ||
454 !audio_buffer_queue_.empty()) {
455 new_buffers_cb_.Run(audio_buffer_queue_, video_buffer_queue_);
456 audio_buffer_queue_.clear();
457 video_buffer_queue_.clear();
458 }
459 }
460
461 void Mpeg2TsStreamParser::StartSegmentIfNeeded() {
462 if (segment_started_) {
463 return;
464 }
465 LOG(INFO) << "Starting a new segment";
466 segment_started_ = true;
467 new_segment_cb_.Run();
468 }
469
470 } // namespace mpeg2ts
471 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698