summaryrefslogtreecommitdiffstats
path: root/media/webm
diff options
context:
space:
mode:
Diffstat (limited to 'media/webm')
-rw-r--r--media/webm/webm_stream_parser.cc350
-rw-r--r--media/webm/webm_stream_parser.h67
2 files changed, 417 insertions, 0 deletions
diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc
new file mode 100644
index 0000000..45c57f3
--- /dev/null
+++ b/media/webm/webm_stream_parser.cc
@@ -0,0 +1,350 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/webm/webm_stream_parser.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "media/ffmpeg/ffmpeg_common.h"
+#include "media/filters/ffmpeg_glue.h"
+#include "media/filters/in_memory_url_protocol.h"
+#include "media/webm/webm_cluster_parser.h"
+#include "media/webm/webm_constants.h"
+#include "media/webm/webm_info_parser.h"
+#include "media/webm/webm_tracks_parser.h"
+
+namespace media {
+
+// Helper class that uses FFmpeg to create AudioDecoderConfig &
+// VideoDecoderConfig objects.
+//
+// This dependency on FFmpeg can be removed once we update WebMTracksParser
+// to parse the necessary data to construct AudioDecoderConfig &
+// VideoDecoderConfig objects. http://crbug.com/108756
+class FFmpegConfigHelper {
+ public:
+ FFmpegConfigHelper();
+ ~FFmpegConfigHelper();
+
+ bool Parse(const uint8* data, int size);
+
+ const AudioDecoderConfig& audio_config() const;
+ const VideoDecoderConfig& video_config() const;
+
+ private:
+ AVFormatContext* CreateFormatContext(const uint8* data, int size);
+ bool SetupStreamConfigs();
+
+ AudioDecoderConfig audio_config_;
+ VideoDecoderConfig video_config_;
+
+ // Backing buffer for |url_protocol_|.
+ scoped_array<uint8> url_protocol_buffer_;
+
+ // Protocol used by |format_context_|. It must outlive the context object.
+ scoped_ptr<FFmpegURLProtocol> url_protocol_;
+
+ // FFmpeg format context for this demuxer. It is created by
+ // av_open_input_file() during demuxer initialization and cleaned up with
+ // DestroyAVFormatContext() in the destructor.
+ AVFormatContext* format_context_;
+
+ static const uint8 kWebMHeader[];
+ static const int kSegmentSizeOffset;
+ static const uint8 kEmptyCluster[];
+
+ DISALLOW_COPY_AND_ASSIGN(FFmpegConfigHelper);
+};
+
+// WebM File Header. This is prepended to the INFO & TRACKS
+// data passed to Init() before handing it to FFmpeg. Essentially
+// we are making the INFO & TRACKS data look like a small WebM
+// file so we can use FFmpeg to initialize the AVFormatContext.
+const uint8 FFmpegConfigHelper::kWebMHeader[] = {
+ 0x1A, 0x45, 0xDF, 0xA3, 0x9F, // EBML (size = 0x1f)
+ 0x42, 0x86, 0x81, 0x01, // EBMLVersion = 1
+ 0x42, 0xF7, 0x81, 0x01, // EBMLReadVersion = 1
+ 0x42, 0xF2, 0x81, 0x04, // EBMLMaxIDLength = 4
+ 0x42, 0xF3, 0x81, 0x08, // EBMLMaxSizeLength = 8
+ 0x42, 0x82, 0x84, 0x77, 0x65, 0x62, 0x6D, // DocType = "webm"
+ 0x42, 0x87, 0x81, 0x02, // DocTypeVersion = 2
+ 0x42, 0x85, 0x81, 0x02, // DocTypeReadVersion = 2
+ // EBML end
+ 0x18, 0x53, 0x80, 0x67, // Segment
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segment(size = 0)
+ // INFO goes here.
+};
+
+// Offset of the segment size field in kWebMHeader. Used to update
+// the segment size field before handing the buffer to FFmpeg.
+const int FFmpegConfigHelper::kSegmentSizeOffset = sizeof(kWebMHeader) - 8;
+
+const uint8 FFmpegConfigHelper::kEmptyCluster[] = {
+ 0x1F, 0x43, 0xB6, 0x75, 0x80 // CLUSTER (size = 0)
+};
+
+FFmpegConfigHelper::FFmpegConfigHelper() : format_context_(NULL) {}
+
+FFmpegConfigHelper::~FFmpegConfigHelper() {
+ if (!format_context_)
+ return;
+
+ DestroyAVFormatContext(format_context_);
+ format_context_ = NULL;
+
+ if (url_protocol_.get()) {
+ FFmpegGlue::GetInstance()->RemoveProtocol(url_protocol_.get());
+ url_protocol_.reset();
+ url_protocol_buffer_.reset();
+ }
+}
+
+bool FFmpegConfigHelper::Parse(const uint8* data, int size) {
+ format_context_ = CreateFormatContext(data, size);
+ return format_context_ && SetupStreamConfigs();
+}
+
+const AudioDecoderConfig& FFmpegConfigHelper::audio_config() const {
+ return audio_config_;
+}
+
+const VideoDecoderConfig& FFmpegConfigHelper::video_config() const {
+ return video_config_;
+}
+
+AVFormatContext* FFmpegConfigHelper::CreateFormatContext(const uint8* data,
+ int size) {
+ DCHECK(!url_protocol_.get());
+ DCHECK(!url_protocol_buffer_.get());
+
+ int segment_size = size + sizeof(kEmptyCluster);
+ int buf_size = sizeof(kWebMHeader) + segment_size;
+ url_protocol_buffer_.reset(new uint8[buf_size]);
+ uint8* buf = url_protocol_buffer_.get();
+ memcpy(buf, kWebMHeader, sizeof(kWebMHeader));
+ memcpy(buf + sizeof(kWebMHeader), data, size);
+ memcpy(buf + sizeof(kWebMHeader) + size, kEmptyCluster,
+ sizeof(kEmptyCluster));
+
+ // Update the segment size in the buffer.
+ int64 tmp = (segment_size & GG_LONGLONG(0x00FFFFFFFFFFFFFF)) |
+ GG_LONGLONG(0x0100000000000000);
+ for (int i = 0; i < 8; i++) {
+ buf[kSegmentSizeOffset + i] = (tmp >> (8 * (7 - i))) & 0xff;
+ }
+
+ url_protocol_.reset(new InMemoryUrlProtocol(buf, buf_size, true));
+ std::string key = FFmpegGlue::GetInstance()->AddProtocol(url_protocol_.get());
+
+ // Open FFmpeg AVFormatContext.
+ AVFormatContext* context = NULL;
+ int result = av_open_input_file(&context, key.c_str(), NULL, 0, NULL);
+
+ if (result < 0)
+ return NULL;
+
+ return context;
+}
+
+bool FFmpegConfigHelper::SetupStreamConfigs() {
+ int result = av_find_stream_info(format_context_);
+
+ if (result < 0)
+ return false;
+
+ bool no_supported_streams = true;
+ for (size_t i = 0; i < format_context_->nb_streams; ++i) {
+ AVStream* stream = format_context_->streams[i];
+ AVCodecContext* codec_context = stream->codec;
+ AVMediaType codec_type = codec_context->codec_type;
+
+ if (codec_type == AVMEDIA_TYPE_AUDIO &&
+ stream->codec->codec_id == CODEC_ID_VORBIS &&
+ !audio_config_.IsValidConfig()) {
+ AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_);
+ no_supported_streams = false;
+ continue;
+ }
+
+ if (codec_type == AVMEDIA_TYPE_VIDEO &&
+ stream->codec->codec_id == CODEC_ID_VP8 &&
+ !video_config_.IsValidConfig()) {
+ AVStreamToVideoDecoderConfig(stream, &video_config_);
+ no_supported_streams = false;
+ continue;
+ }
+ }
+
+ return !no_supported_streams;
+}
+
+WebMStreamParser::WebMStreamParser()
+ : state_(WAITING_FOR_INIT),
+ host_(NULL) {
+}
+
+WebMStreamParser::~WebMStreamParser() {}
+
+void WebMStreamParser::Init(const InitCB& init_cb, StreamParserHost* host) {
+ DCHECK_EQ(state_, WAITING_FOR_INIT);
+ DCHECK(init_cb_.is_null());
+ DCHECK(!host_);
+ DCHECK(!init_cb.is_null());
+ DCHECK(host);
+
+ ChangeState(PARSING_HEADERS);
+ init_cb_ = init_cb;
+ host_ = host;
+}
+
+void WebMStreamParser::Flush() {
+ DCHECK_NE(state_, WAITING_FOR_INIT);
+
+ if (state_ != PARSING_CLUSTERS)
+ return;
+
+ cluster_parser_->Reset();
+}
+
+int WebMStreamParser::Parse(const uint8* buf, int size) {
+ DCHECK_NE(state_, WAITING_FOR_INIT);
+
+ if (state_ == PARSING_HEADERS)
+ return ParseInfoAndTracks(buf, size);
+
+ if (state_ == PARSING_CLUSTERS)
+ return ParseCluster(buf, size);
+
+ return -1;
+}
+
+void WebMStreamParser::ChangeState(State new_state) {
+ state_ = new_state;
+}
+
+int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) {
+ DCHECK(data);
+ DCHECK_GT(size, 0);
+
+ const uint8* cur = data;
+ int cur_size = size;
+ int bytes_parsed = 0;
+
+ int id;
+ int64 element_size;
+ int result = WebMParseElementHeader(cur, cur_size, &id, &element_size);
+
+ if (result <= 0)
+ return result;
+
+ switch (id) {
+ case kWebMIdEBML:
+ case kWebMIdSeekHead:
+ case kWebMIdVoid:
+ case kWebMIdCRC32:
+ case kWebMIdCues:
+ if (cur_size < (result + element_size)) {
+ // We don't have the whole element yet. Signal we need more data.
+ return 0;
+ }
+ // Skip the element.
+ return result + element_size;
+ break;
+ case kWebMIdSegment:
+ // Just consume the segment header.
+ return result;
+ break;
+ case kWebMIdInfo:
+ // We've found the element we are looking for.
+ break;
+ default:
+ DVLOG(1) << "Unexpected ID 0x" << std::hex << id;
+ return -1;
+ }
+
+ WebMInfoParser info_parser;
+ result = info_parser.Parse(cur, cur_size);
+
+ if (result <= 0)
+ return result;
+
+ cur += result;
+ cur_size -= result;
+ bytes_parsed += result;
+
+ WebMTracksParser tracks_parser(info_parser.timecode_scale());
+ result = tracks_parser.Parse(cur, cur_size);
+
+ if (result <= 0)
+ return result;
+
+ bytes_parsed += result;
+
+ double mult = info_parser.timecode_scale() / 1000.0;
+ base::TimeDelta duration =
+ base::TimeDelta::FromMicroseconds(info_parser.duration() * mult);
+
+ FFmpegConfigHelper config_helper;
+
+ if (!config_helper.Parse(data, bytes_parsed))
+ return -1;
+
+ if (config_helper.audio_config().IsValidConfig())
+ host_->OnNewAudioConfig(config_helper.audio_config());
+
+ if (config_helper.video_config().IsValidConfig())
+ host_->OnNewVideoConfig(config_helper.video_config());
+
+ cluster_parser_.reset(new WebMClusterParser(
+ info_parser.timecode_scale(),
+ tracks_parser.audio_track_num(),
+ tracks_parser.audio_default_duration(),
+ tracks_parser.video_track_num(),
+ tracks_parser.video_default_duration()));
+
+ ChangeState(PARSING_CLUSTERS);
+ init_cb_.Run(true, duration);
+
+ return bytes_parsed;
+}
+
+int WebMStreamParser::ParseCluster(const uint8* data, int size) {
+ if (!cluster_parser_.get())
+ return -1;
+
+ int id;
+ int64 element_size;
+ int result = WebMParseElementHeader(data, size, &id, &element_size);
+
+ if (result <= 0)
+ return result;
+
+ if (id == kWebMIdCues) {
+ if (size < (result + element_size)) {
+ // We don't have the whole element yet. Signal we need more data.
+ return 0;
+ }
+ // Skip the element.
+ return result + element_size;
+ }
+
+ int bytes_parsed = cluster_parser_->Parse(data, size);
+
+ if (bytes_parsed <= 0)
+ return bytes_parsed;
+
+ if (cluster_parser_->audio_buffers().empty() &&
+ cluster_parser_->video_buffers().empty())
+ return bytes_parsed;
+
+ if (!host_->OnAudioBuffers(cluster_parser_->audio_buffers()))
+ return -1;
+
+ if (!host_->OnVideoBuffers(cluster_parser_->video_buffers()))
+ return -1;
+
+ return bytes_parsed;
+}
+
+} // namespace media
diff --git a/media/webm/webm_stream_parser.h b/media/webm/webm_stream_parser.h
new file mode 100644
index 0000000..9b45c44
--- /dev/null
+++ b/media/webm/webm_stream_parser.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_WEBM_WEBM_STREAM_PARSER_H_
+#define MEDIA_WEBM_WEBM_STREAM_PARSER_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/buffers.h"
+#include "media/base/stream_parser.h"
+#include "media/base/video_decoder_config.h"
+#include "media/webm/webm_cluster_parser.h"
+
+namespace media {
+
+class WebMStreamParser : public StreamParser {
+ public:
+ WebMStreamParser();
+ virtual ~WebMStreamParser();
+
+ // StreamParser implementation.
+ virtual void Init(const InitCB& init_cb, StreamParserHost* host) OVERRIDE;
+ virtual void Flush() OVERRIDE;
+ virtual int Parse(const uint8* buf, int size) OVERRIDE;
+
+ private:
+ enum State {
+ WAITING_FOR_INIT,
+ PARSING_HEADERS,
+ PARSING_CLUSTERS
+ };
+
+ void ChangeState(State new_state);
+
+ // Parses WebM Header, Info, Tracks elements. It also skips other level 1
+ // elements that are not used right now. Once the Info & Tracks elements have
+ // been parsed, this method will transition the parser from PARSING_HEADERS to
+ // PARSING_CLUSTERS.
+ //
+ // Returns < 0 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returning > 0 indicates success & the number of bytes parsed.
+ int ParseInfoAndTracks(const uint8* data, int size);
+
+ // Incrementally parses WebM cluster elements. This method also skips
+ // CUES elements if they are encountered since we currently don't use the
+ // data in these elements.
+ //
+ // Returns < 0 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returning > 0 indicates success & the number of bytes parsed.
+ int ParseCluster(const uint8* data, int size);
+
+ State state_;
+ InitCB init_cb_;
+ StreamParserHost* host_;
+
+ scoped_ptr<WebMClusterParser> cluster_parser_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMStreamParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_WEBM_WEBM_STREAM_PARSER_H_