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

Side by Side Diff: media/mp2t/mp2t_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: Improve buffer emission + Cleanup 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/mp2t/mp2t_stream_parser.h"
6
7 #include "base/bind.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/stl_util.h"
10 #include "media/base/audio_decoder_config.h"
11 #include "media/base/buffers.h"
12 #include "media/base/stream_parser_buffer.h"
13 #include "media/base/video_decoder_config.h"
14 #include "media/mp2t/es_parser.h"
15 #include "media/mp2t/es_parser_adts.h"
16 #include "media/mp2t/es_parser_h264.h"
17 #include "media/mp2t/mp2t_common.h"
18 #include "media/mp2t/ts_packet.h"
19 #include "media/mp2t/ts_section.h"
20 #include "media/mp2t/ts_section_pat.h"
21 #include "media/mp2t/ts_section_pes.h"
22 #include "media/mp2t/ts_section_pmt.h"
23
24 namespace media {
25 namespace mp2t {
26
27 enum StreamType {
28 // ISO-13818.1 / ITU H.222 Table 2.34 "Stream type assignments"
29 kStreamTypeMpeg1Audio = 0x3,
30 kStreamTypeAAC = 0xf,
31 kStreamTypeAVC = 0x1b,
32 };
33
34 class PidState {
35 public:
36 enum PidType {
37 kPidPat,
38 kPidPmt,
39 kPidAudioPes,
40 kPidVideoPes,
41 };
42
43 PidState(int pid, PidType pid_tyoe,
44 scoped_ptr<TsSection> section_parser);
45
46 // Extract the content of the TS packet and parse it.
47 // Return true if successful.
48 bool PushTsPacket(const TsPacket& ts_packet);
49
50 // Flush the PID state (possibly emitting some pending frames)
51 // and reset its state.
52 void Flush();
53
54 // Enable/disable the PID.
55 // Disabling a PID will reset its state and ignore any further incoming TS
56 // packets.
57 void Enable();
58 void Disable();
59 bool IsEnabled() const;
60
61 PidType pid_type() const { return pid_type_; }
62
63 private:
64 void ResetState();
65
66 int pid_;
67 PidType pid_type_;
68 scoped_ptr<TsSection> section_parser_;
69
70 bool enable_;
71
72 int continuity_counter_;
73 };
74
75 PidState::PidState(int pid, PidType pid_type,
76 scoped_ptr<TsSection> section_parser)
77 : pid_(pid),
78 pid_type_(pid_type),
79 section_parser_(section_parser.Pass()),
80 enable_(false),
81 continuity_counter_(-1) {
82 DCHECK(section_parser_);
83 }
84
85 bool PidState::PushTsPacket(const TsPacket& ts_packet) {
86 DCHECK_EQ(ts_packet.pid(), pid_);
87
88 // The current PID is not part of the PID filter,
89 // just discard the incoming TS packet.
90 if (!enable_)
91 return true;
92
93 int expected_continuity_counter = (continuity_counter_ + 1) % 16;
94 if (continuity_counter_ >= 0 &&
95 ts_packet.continuity_counter() != expected_continuity_counter) {
96 DVLOG(1) << "TS discontinuity detected for pid: " << pid_;
97 return false;
98 }
99
100 bool status = section_parser_->Parse(
101 ts_packet.payload_unit_start_indicator(),
102 ts_packet.payload(),
103 ts_packet.payload_size());
104
105 // At the minimum, when parsing failed, auto reset the section parser.
106 // Components that use the StreamParser can take further action if needed.
107 if (!status) {
108 DVLOG(1) << "Parsing failed for pid = " << pid_;
109 ResetState();
110 }
111
112 return status;
113 }
114
115 void PidState::Flush() {
116 section_parser_->Flush();
117 ResetState();
118 }
119
120 void PidState::Enable() {
121 enable_ = true;
122 }
123
124 void PidState::Disable() {
125 if (!enable_)
126 return;
127
128 ResetState();
129 enable_ = false;
130 }
131
132 bool PidState::IsEnabled() const {
133 return enable_;
134 }
135
136 void PidState::ResetState() {
137 section_parser_->Reset();
138 continuity_counter_ = -1;
139 }
140
141 Mp2tStreamParser::Mp2tStreamParser()
142 : selected_audio_pid_(-1),
143 selected_video_pid_(-1),
144 is_initialized_(false),
145 segment_started_(false),
146 first_video_frame_in_segment_(true) {
147 }
148
149 Mp2tStreamParser::~Mp2tStreamParser() {
150 STLDeleteValues(&pids_);
151 }
152
153 void Mp2tStreamParser::Init(
154 const InitCB& init_cb,
155 const NewConfigCB& config_cb,
156 const NewBuffersCB& new_buffers_cb,
157 const NewTextBuffersCB& text_cb,
158 const NeedKeyCB& need_key_cb,
159 const AddTextTrackCB& add_text_track_cb,
160 const NewMediaSegmentCB& new_segment_cb,
161 const base::Closure& end_of_segment_cb,
162 const LogCB& log_cb) {
163 DCHECK(!is_initialized_);
164 DCHECK(init_cb_.is_null());
165 DCHECK(!init_cb.is_null());
166 DCHECK(!config_cb.is_null());
167 DCHECK(!new_buffers_cb.is_null());
168 DCHECK(!need_key_cb.is_null());
169 DCHECK(!end_of_segment_cb.is_null());
170
171 init_cb_ = init_cb;
172 config_cb_ = config_cb;
173 new_buffers_cb_ = new_buffers_cb;
174 need_key_cb_ = need_key_cb;
175 new_segment_cb_ = new_segment_cb;
176 end_of_segment_cb_ = end_of_segment_cb;
177 log_cb_ = log_cb;
178 }
179
180 void Mp2tStreamParser::Flush() {
181 DVLOG(1) << "Mp2tStreamParser::Flush";
182
183 // Flush the buffers and reset the pids.
184 for (std::map<int, PidState*>::iterator it = pids_.begin();
185 it != pids_.end(); ++it) {
186 DVLOG(1) << "Flushing PID: " << it->first;
187 PidState* pid_state = it->second;
188 pid_state->Flush();
189 delete pid_state;
190 }
191 pids_.clear();
192 EmitRemainingBuffers();
193 audio_queue_chain_.clear();
194 video_queue_chain_.clear();
195
196 // End of the segment.
197 // Note: does not need to invoke |end_of_segment_cb_| since flushing the
198 // stream parser already involves the end of the current segment.
199 segment_started_ = false;
200 first_video_frame_in_segment_ = true;
201
202 // Remove any bytes left in the TS buffer.
203 // (i.e. any partial TS packet => less than 188 bytes).
204 ts_byte_queue_.Reset();
205
206 // Reset the selected PIDs.
207 selected_audio_pid_ = -1;
208 selected_video_pid_ = -1;
209
210 // Reset the audio and video configs.
211 last_audio_config_ = AudioDecoderConfig();
212 last_video_config_ = VideoDecoderConfig();
213 }
214
215 bool Mp2tStreamParser::Parse(const uint8* buf, int size) {
216 DVLOG(1) << "Mp2tStreamParser::Parse size=" << size;
217
218 // Add the data to the parser state.
219 ts_byte_queue_.Push(buf, size);
220
221 while (true) {
222 const uint8* ts_buffer;
223 int ts_buffer_size;
224 ts_byte_queue_.Peek(&ts_buffer, &ts_buffer_size);
225 if (ts_buffer_size < TsPacket::kPacketSize)
226 break;
227
228 // Synchronization.
229 int skipped_bytes = TsPacket::Sync(ts_buffer, ts_buffer_size);
230 if (skipped_bytes > 0) {
231 DVLOG(1) << "Packet not aligned on a TS syncword:"
232 << " skipped_bytes=" << skipped_bytes;
233 ts_byte_queue_.Pop(skipped_bytes);
234 continue;
235 }
236
237 // Parse the TS header, skipping 1 byte if the header is invalid.
238 scoped_ptr<TsPacket> ts_packet(TsPacket::Parse(ts_buffer, ts_buffer_size));
239 if (!ts_packet) {
240 DVLOG(1) << "Error: invalid TS packet";
241 ts_byte_queue_.Pop(1);
242 continue;
243 }
244 DVLOG(LOG_LEVEL_TS)
245 << "Processing PID=" << ts_packet->pid()
246 << " start_unit=" << ts_packet->payload_unit_start_indicator();
247
248 // Parse the section.
249 std::map<int, PidState*>::iterator it = pids_.find(ts_packet->pid());
250 if (it == pids_.end() &&
251 ts_packet->pid() == TsSection::kPidPat) {
252 // Create the PAT state here if needed.
253 scoped_ptr<TsSection> pat_section_parser(
254 new TsSectionPat(
255 base::Bind(&Mp2tStreamParser::RegisterPmt,
256 base::Unretained(this))));
257 scoped_ptr<PidState> pat_pid_state(
258 new PidState(ts_packet->pid(), PidState::kPidPat,
259 pat_section_parser.Pass()));
260 pat_pid_state->Enable();
261 it = pids_.insert(
262 std::pair<int, PidState*>(ts_packet->pid(),
263 pat_pid_state.release())).first;
264 }
265
266 if (it != pids_.end()) {
267 if (!it->second->PushTsPacket(*ts_packet))
268 return false;
269 } else {
270 DVLOG(LOG_LEVEL_TS) << "Ignoring TS packet for pid: " << ts_packet->pid();
271 }
272
273 // Go to the next packet.
274 ts_byte_queue_.Pop(TsPacket::kPacketSize);
275 }
276
277 // Emit the A/V buffers that kept accumulating during TS parsing.
278 EmitRemainingBuffers();
279
280 return true;
281 }
282
283 void Mp2tStreamParser::RegisterPmt(int program_number, int pmt_pid) {
284 DVLOG(1) << "RegisterPmt:"
285 << " program_number=" << program_number
286 << " pmt_pid=" << pmt_pid;
287
288 // Only one TS program is allowed. Ignore the incoming program map table,
289 // if there is already one registered.
290 for (std::map<int, PidState*>::iterator it = pids_.begin();
291 it != pids_.end(); ++it) {
292 PidState* pid_state = it->second;
293 if (pid_state->pid_type() == PidState::kPidPmt) {
294 int pid = it->first;
295 DVLOG_IF(1, pmt_pid != pid) << "More than one program is defined";
296 return;
297 }
298 }
299
300 // Create the PMT state here if needed.
301 DVLOG(1) << "Create a new PMT parser";
302 scoped_ptr<TsSection> pmt_section_parser(
303 new TsSectionPmt(
304 base::Bind(&Mp2tStreamParser::RegisterPes,
305 base::Unretained(this), pmt_pid)));
306 scoped_ptr<PidState> pmt_pid_state(
307 new PidState(pmt_pid, PidState::kPidPmt, pmt_section_parser.Pass()));
308 pmt_pid_state->Enable();
309 pids_.insert(std::pair<int, PidState*>(pmt_pid, pmt_pid_state.release()));
310 }
311
312 void Mp2tStreamParser::RegisterPes(int pmt_pid,
313 int pes_pid,
314 int stream_type) {
315 // TODO(damienv): check there is no mismatch if the entry already exists.
316 DVLOG(1) << "RegisterPes:"
317 << " pes_pid=" << pes_pid
318 << " stream_type=" << std::hex << stream_type << std::dec;
319 std::map<int, PidState*>::iterator it = pids_.find(pes_pid);
320 if (it != pids_.end())
321 return;
322
323 // Create a stream parser corresponding to the stream type.
324 bool is_audio = false;
325 scoped_ptr<EsParser> es_parser;
326 if (stream_type == kStreamTypeAVC) {
327 es_parser.reset(
328 new EsParserH264(
329 base::Bind(&Mp2tStreamParser::OnVideoConfigChanged,
330 base::Unretained(this),
331 pes_pid),
332 base::Bind(&Mp2tStreamParser::OnEmitVideoBuffer,
333 base::Unretained(this),
334 pes_pid)));
335 } else if (stream_type == kStreamTypeAAC) {
336 es_parser.reset(
337 new EsParserAdts(
338 base::Bind(&Mp2tStreamParser::OnAudioConfigChanged,
339 base::Unretained(this),
340 pes_pid),
341 base::Bind(&Mp2tStreamParser::OnEmitAudioBuffer,
342 base::Unretained(this),
343 pes_pid)));
344 is_audio = true;
345 } else {
346 return;
347 }
348
349 // Create the PES state here.
350 DVLOG(1) << "Create a new PES state";
351 scoped_ptr<TsSection> pes_section_parser(
352 new TsSectionPes(es_parser.Pass()));
353 PidState::PidType pid_type =
354 is_audio ? PidState::kPidAudioPes : PidState::kPidVideoPes;
355 scoped_ptr<PidState> pes_pid_state(
356 new PidState(pes_pid, pid_type, pes_section_parser.Pass()));
357 pids_.insert(std::pair<int, PidState*>(pes_pid, pes_pid_state.release()));
358
359 // The pid filter must be updated.
acolwell GONE FROM CHROMIUM 2013/09/18 01:46:05 nit: Remove comment since it isn't really adding a
damienv1 2013/09/18 21:40:17 Done.
360 UpdatePidFilter();
361 }
362
363 void Mp2tStreamParser::UpdatePidFilter() {
364 // Applies the HLS rule to select the default audio/video PIDs:
365 // select the audio/video streams with the lowest PID.
366 // TODO(damienv): this can be changed when the StreamParser interface
367 // supports multiple audio/video streams.
368 PidMap::iterator lowest_audio_pid = pids_.end();
369 PidMap::iterator lowest_video_pid = pids_.end();
370 for (PidMap::iterator it = pids_.begin(); it != pids_.end(); ++it) {
371 int pid = it->first;
372 PidState* pid_state = it->second;
373 if (pid_state->pid_type() == PidState::kPidAudioPes &&
374 (lowest_audio_pid == pids_.end() || pid < lowest_audio_pid->first))
375 lowest_audio_pid = it;
376 if (pid_state->pid_type() == PidState::kPidVideoPes &&
377 (lowest_video_pid == pids_.end() || pid < lowest_video_pid->first))
378 lowest_video_pid = it;
379 }
380
381 // Enable both the lowest audio and video PIDs.
382 if (lowest_audio_pid != pids_.end()) {
383 DVLOG(1) << "Enable audio pid: " << lowest_audio_pid->first;
384 lowest_audio_pid->second->Enable();
385 selected_audio_pid_ = lowest_audio_pid->first;
386 }
387 if (lowest_video_pid != pids_.end()) {
388 DVLOG(1) << "Enable video pid: " << lowest_audio_pid->first;
389 lowest_video_pid->second->Enable();
390 selected_video_pid_ = lowest_video_pid->first;
391 }
392
393 // Disable all the other audio and video PIDs.
394 for (PidMap::iterator it = pids_.begin(); it != pids_.end(); ++it) {
395 PidState* pid_state = it->second;
396 if (it != lowest_audio_pid && it != lowest_video_pid &&
397 (pid_state->pid_type() == PidState::kPidAudioPes ||
398 pid_state->pid_type() == PidState::kPidVideoPes))
399 pid_state->Disable();
400 }
401 }
402
403 void Mp2tStreamParser::OnVideoConfigChanged(
404 int pes_pid,
405 const VideoDecoderConfig& video_decoder_config) {
406 DVLOG(1) << "OnVideoConfigChanged for pid=" << pes_pid;
407 DCHECK_EQ(pes_pid, selected_video_pid_);
408
409 VideoQueueWithConfig video_queue_with_config;
410 video_queue_with_config.is_config_sent = false;
411 video_queue_with_config.config = video_decoder_config;
412 video_queue_chain_.push_back(video_queue_with_config);
413 FinishInitializationIfNeeded();
414 }
415
416 void Mp2tStreamParser::OnAudioConfigChanged(
417 int pes_pid,
418 const AudioDecoderConfig& audio_decoder_config) {
419 DVLOG(1) << "OnAudioConfigChanged for pid=" << pes_pid;
420 DCHECK_EQ(pes_pid, selected_audio_pid_);
421
422 AudioQueueWithConfig audio_queue_with_config;
423 audio_queue_with_config.is_config_sent = false;
424 audio_queue_with_config.config = audio_decoder_config;
425 audio_queue_chain_.push_back(audio_queue_with_config);
426 FinishInitializationIfNeeded();
427 }
428
429 void Mp2tStreamParser::FinishInitializationIfNeeded() {
430 // Nothing to be done if already initialized.
431 if (is_initialized_)
432 return;
433
434 // Initialization is done when both the audio decoder config
435 // and the video decoder config are known
436 // (for a stream with both audio and video).
437 if (selected_audio_pid_ > 0 && audio_queue_chain_.empty())
438 return;
439 if (selected_video_pid_ > 0 && video_queue_chain_.empty())
440 return;
441
442 // Pass the config before invoking the initialization callback.
443 AudioDecoderConfig audio_config;
444 if (!audio_queue_chain_.empty())
445 audio_config = audio_queue_chain_.front().config;
446 VideoDecoderConfig video_config;
447 if (!video_queue_chain_.empty())
448 video_config = video_queue_chain_.front().config;
449 config_cb_.Run(audio_config, video_config);
450
acolwell GONE FROM CHROMIUM 2013/09/18 01:46:05 Shouldn't the is_config_sent properties be set to
damienv1 2013/09/18 21:40:17 Done.
451 // For Mpeg2 TS, the duration is not known.
452 DVLOG(1) << "Mpeg2TS stream parser initialization done";
453 init_cb_.Run(true, kInfiniteDuration());
454 is_initialized_ = true;
455 }
456
457 void Mp2tStreamParser::OnEmitAudioBuffer(
458 int pes_pid,
459 scoped_refptr<StreamParserBuffer> stream_parser_buffer) {
460 DCHECK_EQ(pes_pid, selected_audio_pid_);
461
462 DVLOG(LOG_LEVEL_ES)
463 << "OnEmitAudioBuffer: "
464 << " size="
465 << stream_parser_buffer->data_size()
466 << " dts="
467 << stream_parser_buffer->GetDecodeTimestamp().InMilliseconds()
468 << " pts="
469 << stream_parser_buffer->timestamp().InMilliseconds();
470 stream_parser_buffer->set_timestamp(
471 stream_parser_buffer->timestamp() - time_offset_);
472 stream_parser_buffer->SetDecodeTimestamp(
473 stream_parser_buffer->GetDecodeTimestamp() - time_offset_);
474
475 // Ignore the incoming buffer if it is not associated with any config.
476 if (audio_queue_chain_.empty()) {
acolwell GONE FROM CHROMIUM 2013/09/18 01:46:05 whoa! How does this happen? ISTM that we should no
damienv1 2013/09/18 21:40:17 For audio (at least for the codecs I know), this s
477 DVLOG(1) << "Ignoring audio buffer with no corresponding audio config";
478 return;
479 }
480
481 // Associate the incoming buffer with the latest video config.
482 audio_queue_chain_.back().buffer_queue.push_back(stream_parser_buffer);
483 }
484
485 void Mp2tStreamParser::OnEmitVideoBuffer(
486 int pes_pid,
487 scoped_refptr<StreamParserBuffer> stream_parser_buffer) {
488 DCHECK_EQ(pes_pid, selected_video_pid_);
489
490 DVLOG(LOG_LEVEL_ES)
491 << "OnEmitVideoBuffer"
492 << " size="
493 << stream_parser_buffer->data_size()
494 << " dts="
495 << stream_parser_buffer->GetDecodeTimestamp().InMilliseconds()
496 << " pts="
497 << stream_parser_buffer->timestamp().InMilliseconds()
498 << " IsKeyframe="
499 << stream_parser_buffer->IsKeyframe();
500 stream_parser_buffer->set_timestamp(
501 stream_parser_buffer->timestamp() - time_offset_);
502 stream_parser_buffer->SetDecodeTimestamp(
503 stream_parser_buffer->GetDecodeTimestamp() - time_offset_);
504
505 // Ignore the incoming buffer if it is not associated with any config.
506 if (video_queue_chain_.empty()) {
acolwell GONE FROM CHROMIUM 2013/09/18 01:46:05 ditto. The parser calling this shouldn't be handin
damienv1 2013/09/18 21:40:17 My goal it to make the ES parser dumb (just segmen
507 DVLOG(1) << "Ignoring video buffer with no corresponding video config";
508 return;
509 }
510
511 // Associate the incoming buffer with the latest video config.
512 video_queue_chain_.back().buffer_queue.push_back(stream_parser_buffer);
513 }
514
515 void Mp2tStreamParser::EmitRemainingBuffers() {
516 DVLOG(LOG_LEVEL_ES) << "Mp2tStreamParser::EmitRemainingBuffers";
517 if (!is_initialized_)
518 return;
519
520 // Buffer emission.
521 while (!audio_queue_chain_.empty() || !video_queue_chain_.empty()) {
522 // Start a segment if needed.
523 if (!segment_started_) {
524 DVLOG(1) << "Starting a new segment";
525 segment_started_ = true;
526 new_segment_cb_.Run();
527 }
528
529 // Update the audio and video config if needed.
530 bool is_new_config = false;
531 if (!audio_queue_chain_.empty() &&
532 !audio_queue_chain_.front().is_config_sent) {
533 is_new_config = true;
534 last_audio_config_ = audio_queue_chain_.front().config;
535 }
536 if (!video_queue_chain_.empty() &&
537 !video_queue_chain_.front().is_config_sent) {
538 is_new_config = true;
539 last_video_config_ = video_queue_chain_.front().config;
540 }
541 if (is_new_config)
542 config_cb_.Run(last_audio_config_, last_video_config_);
acolwell GONE FROM CHROMIUM 2013/09/18 01:46:05 This can return false. If this returns false, it m
damienv1 2013/09/18 21:40:17 Done.
543
544 // Add buffers.
545 StreamParser::BufferQueue default_queue;
546 const StreamParser::BufferQueue& audio_queue = audio_queue_chain_.empty()
547 ? default_queue
548 : audio_queue_chain_.front().buffer_queue;
549 StreamParser::BufferQueue& video_queue = video_queue_chain_.empty()
550 ? default_queue
551 : video_queue_chain_.front().buffer_queue;
552 if (first_video_frame_in_segment_) {
553 // Remove all the leading non key frames.
554 while (!video_queue.empty() && !video_queue.front()->IsKeyframe())
555 video_queue.pop_front();
556 }
557 if (!audio_queue.empty() || !video_queue.empty())
558 new_buffers_cb_.Run(audio_queue, video_queue);
acolwell GONE FROM CHROMIUM 2013/09/18 01:46:05 This can return false. If it returns false, it mus
damienv1 2013/09/18 21:40:17 Done for checking the result of new_buffers_cb_.Ru
559 if (!video_queue.empty())
560 first_video_frame_in_segment_ = false;
561
562 // Remove the current audio/video buffers.
563 if (!audio_queue_chain_.empty())
564 audio_queue_chain_.pop_front();
565 if (!video_queue_chain_.empty())
566 video_queue_chain_.pop_front();
567 }
568
569 // Push an empty queue with the last audio/video config
570 // so that buffers with the same config can be added later on.
571 if (last_audio_config_.IsValidConfig()) {
572 AudioQueueWithConfig audio_queue_with_config;
573 audio_queue_with_config.is_config_sent = true;
574 audio_queue_with_config.config = last_audio_config_;
575 audio_queue_chain_.push_back(audio_queue_with_config);
576 }
577 if (last_video_config_.IsValidConfig()) {
578 VideoQueueWithConfig video_queue_with_config;
579 video_queue_with_config.is_config_sent = true;
580 video_queue_with_config.config = last_video_config_;
581 video_queue_chain_.push_back(video_queue_with_config);
582 }
583 }
584
585 } // namespace mp2t
586 } // namespace media
587
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698