Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| OLD | NEW |