Motown: ffmpeg implementations of framework 'parts'
ffmpeg_video_decoder.* is not yet functional

R=johngro@google.com

Review URL: https://codereview.chromium.org/1686363002 .
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index da32a26..8505a9f 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -914,6 +914,14 @@
 
   cflags += default_warning_flags
   cflags_cc += default_warning_flags_cc
+
+  if (is_clang) {
+    cflags += [
+      # TODO(dalesat): Remove once not broken by third party (ffmpeg).
+      # See https://github.com/domokit/mojo/issues/692.
+      "-Wno-constant-conversion",
+    ]
+  }
 }
 
 # rtti ------------------------------------------------------------------------
diff --git a/services/media/BUILD.gn b/services/media/BUILD.gn
index 969dc99..a46edc6 100644
--- a/services/media/BUILD.gn
+++ b/services/media/BUILD.gn
@@ -8,6 +8,7 @@
     "//services/media/common",
     "//services/media/framework",
     "//services/media/framework_create",
+    "//services/media/framework_ffmpeg",
     "//services/media/framework_mojo",
   ]
 }
diff --git a/services/media/framework/parts/decoder.h b/services/media/framework/parts/decoder.h
index 579ce37..ed9e1e0 100644
--- a/services/media/framework/parts/decoder.h
+++ b/services/media/framework/parts/decoder.h
@@ -26,10 +26,6 @@
 
   // Returns the type of the stream the decoder will produce.
   virtual std::unique_ptr<StreamType> output_stream_type() = 0;
-
- protected:
-  // Initializes the decoder. Called by Decoder::Create.
-  virtual Result Init(const StreamType& stream_type) = 0;
 };
 
 }  // namespace media
diff --git a/services/media/framework/parts/file_reader.cc b/services/media/framework/parts/file_reader.cc
index 6c68d05..623e1e4 100644
--- a/services/media/framework/parts/file_reader.cc
+++ b/services/media/framework/parts/file_reader.cc
@@ -40,7 +40,7 @@
   return Result::kOk;
 }
 
-size_t FileReader::Read(uint8* buffer, int bytes_to_read) {
+size_t FileReader::Read(uint8_t* buffer, size_t bytes_to_read) {
   return fread(buffer, 1, bytes_to_read, file_);
 }
 
@@ -48,7 +48,7 @@
   return ftell(file_);
 }
 
-int64_t FileReader::SetPosition(int64 position) {
+int64_t FileReader::SetPosition(int64_t position) {
   if (fseek(file_, position, SEEK_SET) < 0) {
     return -1;
   }
diff --git a/services/media/framework/parts/file_reader.h b/services/media/framework/parts/file_reader.h
index 5449286..c25291c 100644
--- a/services/media/framework/parts/file_reader.h
+++ b/services/media/framework/parts/file_reader.h
@@ -22,11 +22,11 @@
   // Reader implementation.
   Result Init(const GURL& gurl) override;
 
-  size_t Read(uint8* buffer, int bytes_to_read) override;
+  size_t Read(uint8_t* buffer, size_t bytes_to_read) override;
 
   int64_t GetPosition() const override;
 
-  int64_t SetPosition(int64 position) override;
+  int64_t SetPosition(int64_t position) override;
 
   size_t GetSize() const override;
 
@@ -36,7 +36,7 @@
   FileReader() {}
 
   FILE* file_;
-  int64 size_;
+  size_t size_;
 };
 
 }  // namespace media
diff --git a/services/media/framework/parts/reader.h b/services/media/framework/parts/reader.h
index dc07ad8..8cf9c20 100644
--- a/services/media/framework/parts/reader.h
+++ b/services/media/framework/parts/reader.h
@@ -27,14 +27,14 @@
 
   // Reads the given number of bytes into the buffer and returns the number of
   // bytes read. Returns -1 if the operation fails.
-  virtual size_t Read(uint8* buffer, int bytes_to_read) = 0;
+  virtual size_t Read(uint8_t* buffer, size_t bytes_to_read) = 0;
 
   // Gets the current position or -1 if the operation fails.
   virtual int64_t GetPosition() const = 0;
 
   // Seeks to the given position and returns it. Returns -1 if the operation
   // fails.
-  virtual int64_t SetPosition(int64 position) = 0;
+  virtual int64_t SetPosition(int64_t position) = 0;
 
   // Returns the file size. Returns -1 if the operation fails or the size isn't
   // known.
diff --git a/services/media/framework_create/BUILD.gn b/services/media/framework_create/BUILD.gn
index b909fdb..f383688 100644
--- a/services/media/framework_create/BUILD.gn
+++ b/services/media/framework_create/BUILD.gn
@@ -13,5 +13,6 @@
 
   deps = [
     "//services/media/framework",
+    "//services/media/framework_ffmpeg",
   ]
 }
diff --git a/services/media/framework_create/decoder.cc b/services/media/framework_create/decoder.cc
index 1c0fa3a..fd3c06d 100644
--- a/services/media/framework_create/decoder.cc
+++ b/services/media/framework_create/decoder.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "services/media/framework/parts/decoder.h"
+#include "services/media/framework_ffmpeg/ffmpeg_decoder.h"
 
 namespace mojo {
 namespace media {
@@ -10,7 +11,13 @@
 Result Decoder::Create(
     const StreamType& stream_type,
     std::shared_ptr<Decoder>* decoder_out) {
-  return Result::kUnsupportedOperation;
+  std::shared_ptr<Decoder> decoder;
+  Result result = FfmpegDecoder::Create(stream_type, &decoder);
+  if (result == Result::kOk) {
+    *decoder_out = decoder;
+  }
+
+  return result;
 }
 
 } // namespace media
diff --git a/services/media/framework_create/demux.cc b/services/media/framework_create/demux.cc
index c763c1b..5285b8b 100644
--- a/services/media/framework_create/demux.cc
+++ b/services/media/framework_create/demux.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "services/media/framework/parts/demux.h"
+#include "services/media/framework_ffmpeg/ffmpeg_demux.h"
 
 namespace mojo {
 namespace media {
@@ -10,7 +11,14 @@
 Result Demux::Create(
     std::shared_ptr<Reader> reader,
     std::shared_ptr<Demux>* demux_out) {
-  return Result::kUnsupportedOperation;
+  std::shared_ptr<Demux> demux = FfmpegDemux::Create();
+
+  Result result = demux->Init(reader);
+  if (result == Result::kOk) {
+    *demux_out = demux;
+  }
+
+  return result;
 }
 
 } // namespace media
diff --git a/services/media/framework_ffmpeg/BUILD.gn b/services/media/framework_ffmpeg/BUILD.gn
new file mode 100644
index 0000000..8934bc5
--- /dev/null
+++ b/services/media/framework_ffmpeg/BUILD.gn
@@ -0,0 +1,60 @@
+# Copyright 2016 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.
+
+import("//build/module_args/mojo.gni")
+import("$mojo_sdk_root/mojo/public/mojo_sdk.gni")
+
+config("default_include_dirs") {
+  include_dirs = [
+    "//",
+    root_gen_dir,
+    "//third_party/ffmpeg",
+  ]
+
+  # TODO(dalesat): Why is this needed?
+  if (is_android) {
+    include_dirs +=
+        [ "//third_party/ffmpeg/chromium/config/Chromium/android/arm" ]
+  }
+}
+
+source_set("framework_ffmpeg") {
+  sources = [
+    "ffmpeg_audio_decoder.cc",
+    "ffmpeg_audio_decoder.h",
+    "ffmpeg_decoder.cc",
+    "ffmpeg_decoder.h",
+    "ffmpeg_decoder_base.cc",
+    "ffmpeg_decoder_base.h",
+    "ffmpeg_demux.cc",
+    "ffmpeg_demux.h",
+    "ffmpeg_formatting.cc",
+    "ffmpeg_formatting.h",
+    "ffmpeg_init.cc",
+    "ffmpeg_init.h",
+    "ffmpeg_io.cc",
+    "ffmpeg_io.h",
+    "ffmpeg_type_converters.cc",
+    "ffmpeg_type_converters.h",
+    "ffmpeg_video_decoder.cc",
+    "ffmpeg_video_decoder.h",
+  ]
+
+  deps = [
+    "//base",
+    "//mojo/common",
+    "//services/media/framework",
+    "//third_party/ffmpeg",
+  ]
+
+  defines = [
+    "FF_API_PIX_FMT_DESC=0",
+    "FF_API_OLD_DECODE_AUDIO=0",
+    "FF_API_DESTRUCT_PACKET=0",
+    "FF_API_GET_BUFFER=0",
+  ]
+
+  configs -= [ "//build/config/compiler:default_include_dirs" ]
+  configs += [ ":default_include_dirs" ]
+}
diff --git a/services/media/framework_ffmpeg/ffmpeg_audio_decoder.cc b/services/media/framework_ffmpeg/ffmpeg_audio_decoder.cc
new file mode 100644
index 0000000..a2d425e
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_audio_decoder.cc
@@ -0,0 +1,205 @@
+// Copyright 2016 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 "base/logging.h"
+#include "services/media/framework_ffmpeg/ffmpeg_audio_decoder.h"
+
+namespace mojo {
+namespace media {
+
+FfmpegAudioDecoder::FfmpegAudioDecoder(AvCodecContextPtr av_codec_context) :
+    FfmpegDecoderBase(std::move(av_codec_context)) {
+  DCHECK(context());
+  DCHECK(context()->channels > 0);
+
+  context()->opaque = this;
+  context()->get_buffer2 = AllocateBufferForAvFrame;
+  context()->refcounted_frames = 1;
+
+  if (av_sample_fmt_is_planar(context()->sample_fmt)) {
+    // Prepare for interleaving.
+    stream_type_ = output_stream_type();
+    lpcm_util_ = LpcmUtil::Create(*stream_type_->lpcm());
+  }
+}
+
+FfmpegAudioDecoder::~FfmpegAudioDecoder() {}
+
+int FfmpegAudioDecoder::Decode(
+    PayloadAllocator* allocator,
+    bool* frame_decoded_out) {
+  DCHECK(allocator);
+  DCHECK(frame_decoded_out);
+  DCHECK(context());
+  DCHECK(frame());
+
+  // Use the provided allocator (for allocations in AllocateBufferForAvFrame)
+  // unless we intend to interleave later, in which case use the default
+  // allocator. We'll interleave into a buffer from the provided allocator
+  // in CreateOutputPacket.
+  allocator_ = lpcm_util_ ? PayloadAllocator::GetDefault() : allocator;
+
+  int frame_decoded = 0;
+  int input_bytes_used = avcodec_decode_audio4(
+      context().get(),
+      frame().get(),
+      &frame_decoded,
+      &packet());
+  *frame_decoded_out = frame_decoded != 0;
+
+  // We're done with this allocator.
+  allocator_ = nullptr;
+
+  return input_bytes_used;
+}
+
+PacketPtr FfmpegAudioDecoder::CreateOutputPacket(PayloadAllocator* allocator) {
+  DCHECK(allocator);
+  DCHECK(frame());
+
+  int64_t presentation_time = frame()->pts;
+  if (presentation_time == AV_NOPTS_VALUE) {
+    // TODO(dalesat): Adjust next_presentation_time_ for seek/non-zero start.
+    presentation_time = next_presentation_time_;
+    next_presentation_time_ += frame()->nb_samples;
+  }
+
+  uint64_t payload_size;
+  void *payload_buffer;
+
+  AvBufferContext* av_buffer_context =
+      reinterpret_cast<AvBufferContext*>(av_buffer_get_opaque(frame()->buf[0]));
+
+  if (lpcm_util_) {
+    // We need to interleave. The non-interleaved frames are in a buffer that
+    // was allocated from the default allocator. That buffer will get released
+    // later in ReleaseBufferForAvFrame. We need a new buffer for the
+    // interleaved frames, which we get from the provided allocator.
+    DCHECK(stream_type_);
+    DCHECK(stream_type_->lpcm());
+    payload_size = stream_type_->lpcm()->min_buffer_size(frame()->nb_samples);
+    payload_buffer = allocator->AllocatePayloadBuffer(payload_size);
+
+    lpcm_util_->Interleave(
+        av_buffer_context->buffer(),
+        av_buffer_context->size(),
+        payload_buffer,
+        frame()->nb_samples);
+  } else {
+    // We don't need to interleave. The interleaved frames are in a buffer that
+    // was allocated from the correct allocator. We take ownership of the buffer
+    // by calling Release here so that ReleaseBufferForAvFrame won't release it.
+    payload_size = av_buffer_context->size();
+    payload_buffer = av_buffer_context->Release();
+  }
+
+  return Packet::Create(
+      presentation_time,
+      frame()->nb_samples,
+      false, // The base class is responsible for end-of-stream.
+      payload_size,
+      payload_buffer,
+      allocator);
+}
+
+PacketPtr FfmpegAudioDecoder::CreateOutputEndOfStreamPacket() {
+  return Packet::CreateEndOfStream(next_presentation_time_);
+}
+
+int FfmpegAudioDecoder::AllocateBufferForAvFrame(
+    AVCodecContext* av_codec_context,
+    AVFrame* av_frame,
+    int flags) {
+  // CODEC_CAP_DR1 is required in order to do allocation this way.
+  DCHECK(av_codec_context->codec->capabilities & CODEC_CAP_DR1);
+
+  FfmpegAudioDecoder* self =
+      reinterpret_cast<FfmpegAudioDecoder*>(av_codec_context->opaque);
+  DCHECK(self);
+  DCHECK(self->allocator_);
+
+  AVSampleFormat av_sample_format =
+      static_cast<AVSampleFormat>(av_frame->format);
+
+  int buffer_size = av_samples_get_buffer_size(
+      &av_frame->linesize[0],
+      av_codec_context->channels,
+      av_frame->nb_samples,
+      av_sample_format,
+      FfmpegAudioDecoder::kChannelAlign);
+  if (buffer_size < 0) {
+    LOG(WARNING) << "av_samples_get_buffer_size failed";
+    return buffer_size;
+  }
+
+  AvBufferContext* av_buffer_context =
+      new AvBufferContext(buffer_size, self->allocator_);
+  uint8_t* buffer = av_buffer_context->buffer();
+
+  if (!av_sample_fmt_is_planar(av_sample_format)) {
+    // Samples are interleaved. There's just one buffer.
+    av_frame->data[0] = buffer;
+  } else {
+    // Samples are not interleaved. There's one buffer per channel.
+    int channels = av_codec_context->channels;
+    int bytes_per_channel = buffer_size / channels;
+    uint8_t* channel_buffer = buffer;
+
+    DCHECK(buffer != nullptr || bytes_per_channel == 0);
+
+    if (channels <= AV_NUM_DATA_POINTERS) {
+      // The buffer pointers will fit in av_frame->data.
+      DCHECK_EQ(av_frame->extended_data, av_frame->data);
+      for (int channel = 0; channel < channels; ++channel) {
+        av_frame->data[channel] = channel_buffer;
+        channel_buffer += bytes_per_channel;
+      }
+    } else {
+      // Too many channels for av_frame->data. We have to use
+      // av_frame->extended_data
+      av_frame->extended_data = static_cast<uint8_t**>(
+          av_malloc(channels * sizeof(*av_frame->extended_data)));
+
+      // The first AV_NUM_DATA_POINTERS go in both data and extended_data.
+      int channel = 0;
+      for (; channel < AV_NUM_DATA_POINTERS; ++channel) {
+        av_frame->extended_data[channel] = av_frame->data[channel] =
+            channel_buffer;
+        channel_buffer += bytes_per_channel;
+      }
+
+      // The rest go only in extended_data.
+      for (; channel < channels; ++channel) {
+        av_frame->extended_data[channel] = channel_buffer;
+        channel_buffer += bytes_per_channel;
+      }
+    }
+  }
+
+  av_frame->buf[0] = av_buffer_create(
+      buffer,
+      buffer_size,
+      ReleaseBufferForAvFrame,
+      av_buffer_context,
+      0); // flags
+
+  return 0;
+}
+
+void FfmpegAudioDecoder::ReleaseBufferForAvFrame(
+    void* opaque,
+    uint8_t* buffer) {
+  AvBufferContext* av_buffer_context =
+      reinterpret_cast<AvBufferContext*>(opaque);
+  DCHECK(av_buffer_context);
+  // Either this buffer has already been released to someone else's ownership,
+  // or it's the same as the buffer parameter.
+  DCHECK(
+      av_buffer_context->buffer() == nullptr ||
+      av_buffer_context->buffer() == buffer);
+  delete av_buffer_context;
+}
+
+} // namespace media
+} // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_audio_decoder.h b/services/media/framework_ffmpeg/ffmpeg_audio_decoder.h
new file mode 100644
index 0000000..e537b34
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_audio_decoder.h
@@ -0,0 +1,113 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_AUDIO_DECODER_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_AUDIO_DECODER_H_
+
+#include "services/media/framework/lpcm_util.h"
+#include "services/media/framework_ffmpeg/ffmpeg_decoder_base.h"
+
+namespace mojo {
+namespace media {
+
+// Decoder implementation employing an ffmpeg audio decoder.
+class FfmpegAudioDecoder : public FfmpegDecoderBase {
+ public:
+  FfmpegAudioDecoder(AvCodecContextPtr av_codec_context);
+
+  ~FfmpegAudioDecoder() override;
+
+ protected:
+  // FfmpegDecoderBase overrides.
+  int Decode(PayloadAllocator* allocator, bool* frame_decoded_out) override;
+
+  PacketPtr CreateOutputPacket(PayloadAllocator* allocator) override;
+
+  PacketPtr CreateOutputEndOfStreamPacket() override;
+
+ private:
+  // Used to control deallocation of buffers.
+  class AvBufferContext {
+   public:
+    AvBufferContext(size_t size, PayloadAllocator* allocator) :
+        size_(size),
+        allocator_(allocator) {
+      DCHECK(allocator_);
+      if (size_ == 0) {
+        buffer_ = nullptr;
+      } else {
+        buffer_ = static_cast<uint8_t*>(
+            allocator_->AllocatePayloadBuffer(size_));
+      }
+    }
+
+    ~AvBufferContext() {
+      if (allocator_ == nullptr) {
+        // Previously released.
+        return;
+      }
+
+      if (size_ != 0) {
+        DCHECK(buffer_ != nullptr);
+        allocator_->ReleasePayloadBuffer(size_, buffer_);
+        return;
+      }
+
+      DCHECK(buffer_ == nullptr);
+    }
+
+    uint8_t* buffer() { return buffer_; }
+
+    size_t size() { return size_; }
+
+    // Releases ownership of the buffer.
+    uint8_t* Release() {
+      DCHECK(allocator_) << "AvBufferContext released twice";
+      uint8_t* result = buffer_;
+      buffer_ = nullptr;
+      size_ = 0;
+      allocator_ = nullptr;
+      return result;
+    }
+
+   private:
+    uint8_t* buffer_;
+    size_t size_;
+    PayloadAllocator* allocator_;
+  };
+
+  // Align sample buffers on 32-byte boundaries. This is the value that Chromium
+  // uses and is supposed to work for all processor architectures. Strangely, if
+  // we were to tell ffmpeg to use the default (by passing 0), it aligns on 32
+  // sample (not byte) boundaries.
+  static const int kChannelAlign = 32;
+
+  // Callback used by the ffmpeg decoder to acquire a buffer.
+  static int AllocateBufferForAvFrame(
+      AVCodecContext* av_codec_context,
+      AVFrame* av_frame,
+      int flags);
+
+  // Callback used by the ffmpeg decoder to release a buffer.
+  static void ReleaseBufferForAvFrame(void* opaque, uint8_t* buffer);
+
+  // The allocator used by avcodec_decode_audio4 to provide context for
+  // AllocateBufferForAvFrame. This is set only during the call to
+  // avcodec_decode_audio4.
+  PayloadAllocator* allocator_;
+
+  // For interleaving, if needed.
+  std::unique_ptr<LpcmUtil> lpcm_util_;
+
+  // For interleaving, if needed.
+  std::unique_ptr<StreamType> stream_type_;
+
+  // Used to supply missing PTS.
+  int64_t next_presentation_time_= 0;
+};
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_AUDIO_DECODER_H_
diff --git a/services/media/framework_ffmpeg/ffmpeg_decoder.cc b/services/media/framework_ffmpeg/ffmpeg_decoder.cc
new file mode 100644
index 0000000..f99dc5f
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_decoder.cc
@@ -0,0 +1,50 @@
+// Copyright 2016 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 "services/media/framework_ffmpeg/ffmpeg_audio_decoder.h"
+#include "services/media/framework_ffmpeg/ffmpeg_decoder.h"
+#include "services/media/framework_ffmpeg/ffmpeg_type_converters.h"
+#include "services/media/framework_ffmpeg/ffmpeg_video_decoder.h"
+
+namespace mojo {
+namespace media {
+
+Result FfmpegDecoder::Create(
+    const StreamType& stream_type,
+    std::shared_ptr<Decoder>* decoder_out) {
+  DCHECK(decoder_out);
+
+  AvCodecContextPtr av_codec_context(AVCodecContextFromStreamType(stream_type));
+  if (!av_codec_context) {
+    return Result::kUnsupportedOperation;
+  }
+
+  AVCodec* ffmpeg_decoder = avcodec_find_decoder(av_codec_context->codec_id);
+  if (ffmpeg_decoder == nullptr) {
+    return Result::kUnsupportedOperation;
+  }
+
+  int r = avcodec_open2(av_codec_context.get(), ffmpeg_decoder, nullptr);
+  if (r < 0) {
+    return Result::kUnknownError;
+  }
+
+  switch (av_codec_context->codec_type) {
+    case AVMEDIA_TYPE_AUDIO:
+      *decoder_out = std::shared_ptr<Decoder>(
+          new FfmpegAudioDecoder(std::move(av_codec_context)));
+      break;
+    case AVMEDIA_TYPE_VIDEO:
+      *decoder_out = std::shared_ptr<Decoder>(
+          new FfmpegVideoDecoder(std::move(av_codec_context)));
+      break;
+    default:
+      return Result::kUnsupportedOperation;
+  }
+
+  return Result::kOk;
+}
+
+} // namespace media
+} // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_decoder.h b/services/media/framework_ffmpeg/ffmpeg_decoder.h
new file mode 100644
index 0000000..8f316d0
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_decoder.h
@@ -0,0 +1,29 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DECODER_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DECODER_H_
+
+#include <memory>
+
+#include "services/media/framework/parts/decoder.h"
+
+namespace mojo {
+namespace media {
+
+// Abstract base class for ffmpeg-based decoders, just the create function.
+// We don't want the base class implementation here, because we don't want
+// dependent targets to have to deal with ffmpeg includes.
+class FfmpegDecoder : public Decoder {
+ public:
+  // Creates an ffmpeg-based Decoder object for a given media type.
+  static Result Create(
+      const StreamType& stream_type,
+      std::shared_ptr<Decoder>* decoder_out);
+};
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DECODER_H_
diff --git a/services/media/framework_ffmpeg/ffmpeg_decoder_base.cc b/services/media/framework_ffmpeg/ffmpeg_decoder_base.cc
new file mode 100644
index 0000000..c0cb980
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_decoder_base.cc
@@ -0,0 +1,97 @@
+// Copyright 2016 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 "base/logging.h"
+#include "services/media/framework_ffmpeg/ffmpeg_decoder_base.h"
+#include "services/media/framework_ffmpeg/ffmpeg_type_converters.h"
+
+namespace mojo {
+namespace media {
+
+FfmpegDecoderBase::FfmpegDecoderBase(AvCodecContextPtr av_codec_context) :
+    av_codec_context_(std::move(av_codec_context)),
+    av_frame_(av_frame_alloc()) {
+  DCHECK(av_codec_context);
+}
+
+FfmpegDecoderBase::~FfmpegDecoderBase() {}
+
+std::unique_ptr<StreamType> FfmpegDecoderBase::output_stream_type() {
+  return StreamTypeFromAVCodecContext(*av_codec_context_);
+}
+
+void FfmpegDecoderBase::Flush() {
+  DCHECK(av_codec_context_);
+  avcodec_flush_buffers(av_codec_context_.get());
+}
+
+bool FfmpegDecoderBase::TransformPacket(
+    const PacketPtr& input,
+    bool new_input,
+    PayloadAllocator* allocator,
+    PacketPtr* output) {
+  DCHECK(input);
+  DCHECK(allocator);
+  DCHECK(output);
+
+  *output = nullptr;
+
+  if (new_input) {
+    PrepareInputPacket(input);
+  }
+
+  bool frame_decoded = false;
+  int input_bytes_used = Decode(allocator, &frame_decoded);
+  if (input_bytes_used < 0) {
+    // Decode failed.
+    return UnprepareInputPacket(input, output);
+  }
+
+  if (frame_decoded) {
+    DCHECK(allocator);
+    *output = CreateOutputPacket(allocator);
+    av_frame_unref(av_frame_.get());
+  }
+
+  CHECK(input_bytes_used <= av_packet_.size)
+      << "Ffmpeg decoder read beyond end of packet";
+  av_packet_.size -= input_bytes_used;
+  av_packet_.data += input_bytes_used;
+
+  if (av_packet_.size != 0 || (input->end_of_stream() && frame_decoded)) {
+    // The input packet is only partially decoded, or it's an end-of-stream
+    // packet and we're still draining. Let the caller know we want to see the
+    // input packet again.
+    return false;
+  }
+
+  // Used up the whole input packet, and, if we were draining, we're done with
+  // that too.
+  return UnprepareInputPacket(input, output);
+}
+
+void FfmpegDecoderBase::PrepareInputPacket(const PacketPtr& input) {
+  av_init_packet(&av_packet_);
+  av_packet_.data = reinterpret_cast<uint8_t*>(input->payload());
+  av_packet_.size = input->size();
+}
+
+bool FfmpegDecoderBase::UnprepareInputPacket(
+    const PacketPtr& input,
+    PacketPtr* output) {
+  if (input->end_of_stream()) {
+    // Indicate end of stream. This happens when we're draining for the last
+    // time, so there should be no output packet yet.
+    DCHECK(*output == nullptr);
+    *output = CreateOutputEndOfStreamPacket();
+  }
+
+  av_packet_.size = 0;
+  av_packet_.data = nullptr;
+
+  return true;
+}
+
+} // namespace media
+} // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_decoder_base.h b/services/media/framework_ffmpeg/ffmpeg_decoder_base.h
new file mode 100644
index 0000000..833673f
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_decoder_base.h
@@ -0,0 +1,87 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DECODER_BASE_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DECODER_BASE_H_
+
+#include "services/media/framework/parts/decoder.h"
+#include "services/media/framework_ffmpeg/ffmpeg_type_converters.h"
+extern "C" {
+#include "third_party/ffmpeg/libavcodec/avcodec.h"
+}
+
+namespace mojo {
+namespace media {
+
+// Abstract base class for ffmpeg-based decoders.
+class FfmpegDecoderBase : public Decoder {
+ public:
+  FfmpegDecoderBase(AvCodecContextPtr av_codec_context);
+
+  ~FfmpegDecoderBase() override;
+
+  // Decoder implementation.
+  std::unique_ptr<StreamType> output_stream_type() override;
+
+  // Transform implementation.
+  void Flush() override;
+
+  bool TransformPacket(
+      const PacketPtr& input,
+      bool new_input,
+      PayloadAllocator* allocator,
+      PacketPtr* output) override;
+
+ protected:
+  struct AVFrameDeleter {
+    inline void operator()(AVFrame* ptr) const {
+      av_frame_free(&ptr);
+    }
+  };
+
+  // Decodes from av_packet_ into av_frame_. The result indicates how many
+  // bytes were consumed from av_packet_. *frame_decoded_out indicates whether
+  // av_frame_ contains a complete frame.
+  virtual int Decode(PayloadAllocator* allocator, bool* frame_decoded_out) = 0;
+
+  // Creates a Packet from av_frame_.
+  virtual PacketPtr CreateOutputPacket(PayloadAllocator* allocator) = 0;
+
+  // Creates an end-of-stream packet with no payload.
+  virtual PacketPtr CreateOutputEndOfStreamPacket() = 0;
+
+ protected:
+  // The ffmpeg codec context.
+  const AvCodecContextPtr& context() {
+    return av_codec_context_;
+  }
+
+  // Ffmpeg's representation of the input packet.
+  const AVPacket& packet() {
+    return av_packet_;
+  }
+
+  // Ffmpeg's representation of the output packet.
+  const std::unique_ptr<AVFrame, AVFrameDeleter>& frame() {
+    return av_frame_;
+  }
+
+ private:
+  // Prepares to process a new input packet.
+  void PrepareInputPacket(const PacketPtr& input);
+
+  // Finishes up after processing of an input packet has completed, possibly
+  // producing a zero-size end-of-stream packet. Returns true to indicate that
+  // a new input packet is required.
+  bool UnprepareInputPacket(const PacketPtr& input, PacketPtr* output);
+
+  AvCodecContextPtr av_codec_context_;
+  AVPacket av_packet_;
+  std::unique_ptr<AVFrame, AVFrameDeleter> av_frame_;
+};
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DECODER_BASE_H_
diff --git a/services/media/framework_ffmpeg/ffmpeg_demux.cc b/services/media/framework_ffmpeg/ffmpeg_demux.cc
new file mode 100644
index 0000000..c898cc3
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_demux.cc
@@ -0,0 +1,262 @@
+// Copyright 2016 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 <map>
+
+#include "base/logging.h"
+#include "services/media/framework/safe_clone.h"
+#include "services/media/framework_ffmpeg/ffmpeg_demux.h"
+#include "services/media/framework_ffmpeg/ffmpeg_io.h"
+#include "services/media/framework_ffmpeg/ffmpeg_type_converters.h"
+
+namespace mojo {
+namespace media {
+
+class FfmpegDemuxImpl : public FfmpegDemux {
+ public:
+  FfmpegDemuxImpl();
+
+  ~FfmpegDemuxImpl() override;
+
+  // Demux implementation.
+  Result Init(std::shared_ptr<Reader> reader) override;
+
+  std::unique_ptr<Metadata> metadata() const override;
+
+  const std::vector<DemuxStream*>& streams() const override;
+
+  // MultistreamSource implementation.
+  size_t stream_count() const override;
+
+  PacketPtr PullPacket(size_t* stream_index_out) override;
+
+ private:
+  class FfmpegDemuxStream : public DemuxStream {
+   public:
+    FfmpegDemuxStream(const AVFormatContext& format_context, size_t index);
+
+    ~FfmpegDemuxStream() override;
+
+    // Demux::DemuxStream implementation.
+    size_t index() const override;
+
+    std::unique_ptr<StreamType> stream_type() const override;
+
+   private:
+    AVStream* stream_;
+    size_t index_;
+    std::unique_ptr<StreamType> stream_type_;
+  };
+
+  // Specialized packet implementation.
+  class DemuxPacket : public Packet {
+   public:
+    DemuxPacket() {
+      av_init_packet(&av_packet_);
+    }
+
+    // Packet implementation.
+    int64_t presentation_time() const override { return av_packet_.pts; };
+
+    uint64_t duration() const override { return av_packet_.duration; };
+
+    bool end_of_stream() const override { return false; }
+
+    size_t size() const override { return size_t(av_packet_.size); }
+
+    void* payload() const override {
+      return reinterpret_cast<void*>(av_packet_.data);
+    }
+
+    AVPacket& av_packet() {
+      return av_packet_;
+    }
+
+   protected:
+    ~DemuxPacket() override {
+      av_free_packet(&av_packet_);
+    }
+
+    void Release() override { delete this; }
+
+  private:
+    AVPacket av_packet_;
+  };
+
+  struct AVFormatContextDeleter {
+    inline void operator()(AVFormatContext* ptr) const {
+      avformat_free_context(ptr);
+    }
+  };
+
+  // Produces an end-of-stream packet for next_stream_to_end_.
+  PacketPtr PullEndOfStreamPacket(size_t* stream_index_out);
+
+  // Copies metadata from the specified source into map.
+  void CopyMetadata(
+      AVDictionary* source,
+      std::map<std::string, std::string>& map);
+
+  std::shared_ptr<Reader> reader_;
+  std::unique_ptr<AVFormatContext, AVFormatContextDeleter> format_context_;
+  AvioContextPtr io_context_;
+  std::vector<DemuxStream*> streams_;
+  std::unique_ptr<Metadata> metadata_;
+  int64_t next_presentation_time_;
+  int next_stream_to_end_; // -1 means don't end. streams_.size() means stop.
+};
+
+// static
+std::shared_ptr<Demux> FfmpegDemux::Create() {
+  return std::shared_ptr<Demux>(new FfmpegDemuxImpl());
+}
+
+FfmpegDemuxImpl::FfmpegDemuxImpl() : next_stream_to_end_(-1) {}
+
+FfmpegDemuxImpl::~FfmpegDemuxImpl() {}
+
+Result FfmpegDemuxImpl::Init(std::shared_ptr<Reader> reader) {
+  static constexpr uint64_t kNanosecondsPerMicrosecond = 1000;
+
+  reader_ = reader;
+
+  io_context_ = CreateAvioContext(reader.get());
+  if (!io_context_) {
+    LOG(ERROR) << "CreateAvioContext failed (allocation failure)";
+    return Result::kInternalError;
+  }
+
+  // TODO(dalesat): Consider ffmpeg util to centralize memory management.
+  AVFormatContext* format_context = avformat_alloc_context();
+  format_context->flags |= AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_FAST_SEEK;
+  format_context->pb = io_context_.get();
+
+  // TODO(dalesat): This synchronous operation may take a long time.
+  int r = avformat_open_input(&format_context, nullptr, nullptr, nullptr);
+  format_context_.reset(format_context);
+  if (r < 0) {
+    return Result::kInternalError;
+  }
+
+  // TODO(dalesat): This synchronous operation may take a long time.
+  r = avformat_find_stream_info(format_context_.get(), nullptr);
+  if (r < 0) {
+    LOG(ERROR) << "avformat_find_stream_info failed, result " << r;
+    return Result::kInternalError;
+  }
+
+  std::map<std::string, std::string> metadata_map;
+
+  CopyMetadata(format_context_->metadata, metadata_map);
+  for (uint i = 0; i < format_context_->nb_streams; i++) {
+    streams_.push_back(new FfmpegDemuxStream(*format_context_, i));
+    CopyMetadata(format_context_->streams[i]->metadata, metadata_map);
+  }
+
+  metadata_ = Metadata::Create(
+      format_context_->duration * kNanosecondsPerMicrosecond,
+      metadata_map["TITLE"],
+      metadata_map["ARTIST"],
+      metadata_map["ALBUM"],
+      metadata_map["PUBLISHER"],
+      metadata_map["GENRE"],
+      metadata_map["COMPOSER"]);
+
+  return Result::kOk;
+}
+
+std::unique_ptr<Metadata> FfmpegDemuxImpl::metadata() const {
+  return SafeClone(metadata_);
+}
+
+const std::vector<Demux::DemuxStream*>& FfmpegDemuxImpl::streams() const {
+  return streams_;
+}
+
+size_t FfmpegDemuxImpl::stream_count() const {
+  if (!format_context_) {
+    return 0;
+  }
+  return format_context_->nb_streams;
+}
+
+PacketPtr FfmpegDemuxImpl::PullPacket(size_t* stream_index_out) {
+  DCHECK(stream_index_out);
+
+  if (next_stream_to_end_ != -1) {
+    // We're producing end-of-stream packets for all the streams.
+    return PullEndOfStreamPacket(stream_index_out);
+  }
+
+  FfmpegDemuxImpl::DemuxPacket* demux_packet =
+      new FfmpegDemuxImpl::DemuxPacket();
+
+  demux_packet->av_packet().data = nullptr;
+  demux_packet->av_packet().size = 0;
+
+  if (av_read_frame(format_context_.get(), &demux_packet->av_packet()) < 0) {
+    // End of stream. Start producing end-of-stream packets for all the streams.
+    PacketPtr(demux_packet); // Deletes demux_packet.
+    next_stream_to_end_ = 0;
+    return PullEndOfStreamPacket(stream_index_out);
+  }
+
+  *stream_index_out =
+      static_cast<size_t>(demux_packet->av_packet().stream_index);
+  // TODO(dalesat): What if the packet has no PTS or duration?
+  next_presentation_time_ =
+      demux_packet->presentation_time() + demux_packet->duration();
+
+  return PacketPtr(demux_packet);
+}
+
+PacketPtr FfmpegDemuxImpl::PullEndOfStreamPacket(size_t* stream_index_out) {
+  DCHECK(next_stream_to_end_ >= 0);
+
+  if (static_cast<std::size_t>(next_stream_to_end_) >= streams_.size()) {
+    NOTREACHED() << "PullPacket called after all streams have ended";
+    return nullptr;
+  }
+
+  *stream_index_out = next_stream_to_end_++;
+  return Packet::CreateEndOfStream(next_presentation_time_);
+}
+
+void FfmpegDemuxImpl::CopyMetadata(
+    AVDictionary* source,
+    std::map<std::string, std::string>& map) {
+  if (source == nullptr) {
+    return;
+  }
+
+  for (AVDictionaryEntry *entry =
+      av_dict_get(source, "", nullptr, AV_DICT_IGNORE_SUFFIX);
+      entry != nullptr;
+      entry = av_dict_get(source, "", entry, AV_DICT_IGNORE_SUFFIX)) {
+    if (map.find(entry->key) == map.end()) {
+      map.emplace(entry->key, entry->value);
+    }
+  }
+}
+
+FfmpegDemuxImpl::FfmpegDemuxStream::FfmpegDemuxStream(
+    const AVFormatContext& format_context,
+    size_t index) :
+    stream_(format_context.streams[index]), index_(index) {
+  stream_type_ = StreamTypeFromAVCodecContext(*stream_->codec);
+}
+
+FfmpegDemuxImpl::FfmpegDemuxStream::~FfmpegDemuxStream() {}
+
+size_t FfmpegDemuxImpl::FfmpegDemuxStream::index() const {
+  return index_;
+}
+
+std::unique_ptr<StreamType> FfmpegDemuxImpl::FfmpegDemuxStream::stream_type()
+    const {
+  return SafeClone(stream_type_);
+}
+
+} // namespace media
+} // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_demux.h b/services/media/framework_ffmpeg/ffmpeg_demux.h
new file mode 100644
index 0000000..281fb17
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_demux.h
@@ -0,0 +1,23 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DEMUX_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DEMUX_H_
+
+#include <memory>
+
+#include "services/media/framework/parts/demux.h"
+
+namespace mojo {
+namespace media {
+
+class FfmpegDemux : public Demux {
+ public:
+  static std::shared_ptr<Demux> Create();
+};
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_DEMUX_H_
diff --git a/services/media/framework_ffmpeg/ffmpeg_formatting.cc b/services/media/framework_ffmpeg/ffmpeg_formatting.cc
new file mode 100644
index 0000000..2367311
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_formatting.cc
@@ -0,0 +1,805 @@
+// Copyright 2016 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 <ostream>
+
+#include "services/media/framework_ffmpeg/ffmpeg_formatting.h"
+extern "C" {
+#include "third_party/ffmpeg/libavformat/avformat.h"
+#include "third_party/ffmpeg/libavformat/internal.h"
+#include "third_party/ffmpeg/libavutil/dict.h"
+}
+
+namespace mojo {
+namespace media {
+
+const char* safe(const char* s) {
+  return s == nullptr ? "<nullptr>" : s;
+}
+
+std::ostream& operator<<(
+    std::ostream& os,
+    const struct AVCodecTag *const *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else if (*value == nullptr) {
+      return os << "&<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "AVCodecID id: " << (*value)->id << std::endl;
+  os << begl << "unsigned int tag: " << (*value)->tag << std::endl;
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVInputFormat *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "const char *name: " << value->name << std::endl;
+  os << begl << "const char *long_name: " << value->long_name << std::endl;
+  os << begl << "int flags: " << AVFMTFlags(value->flags);
+  os << begl << "const char *extensions: " << safe(value->extensions)
+      << std::endl;
+  os << begl << "const AVCodecTag * const *codec_tag: " << value->codec_tag;
+  os << begl << "const char *mime_type: " << safe(value->mime_type)
+      << std::endl;
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVOutputFormat *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "const char *name: " << safe(value->name) << std::endl;
+  os << begl << "const char *long_name: " << safe(value->long_name)
+      << std::endl;
+  os << begl << "const char *mime_type: " << safe(value->mime_type)
+      << std::endl;
+  os << begl << "const char *extensions: " << safe(value->extensions)
+      << std::endl;
+  os << begl << "AVCodecID audio_codec: " << value->audio_codec;
+  os << begl << "AVCodecID video_codec: " << value->video_codec;
+  os << begl << "AVCodecID subtitle_codec: " << value->subtitle_codec;
+  os << begl << "int flags: " << AVFMTFlags(value->flags);
+  os << begl << "const AVCodecTag * const *codec_tag: " << value->codec_tag;
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVIOContext *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    return os << "TODO" << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, AVFMTCTXFlags value) {
+  if (value.flags_ == 0) {
+    return os << "<none>" << std::endl;
+  }
+
+  if (value.flags_ & AVFMTCTX_NOHEADER) {
+    return os << "AVFMTCTX_NOHEADER" << std::endl;
+  } else {
+    return os << "<UNKNOWN AVFMTCTX_: " << value.flags_ << ">" << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, const AVRational *value) {
+  if (value == nullptr) {
+    return os << "<none>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  for (int index = 0; value->num != 0 || value->den != 0; ++value, ++index) {
+    os << begl << "[" << index << "]: " << *value;
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const int *value) {
+  if (value == nullptr) {
+    return os << "<none>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  for (int index = 0; *value != 0; ++value, ++index) {
+    os << begl << "[" << index << "]: " << *value << std::endl;
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const uint64_t *value) {
+  if (value == nullptr) {
+    return os << "<none>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  for (int index = 0; *value != 0; ++value, ++index) {
+    os << begl << "[" << index << "]: " << *value << std::endl;
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVSampleFormat *value) {
+  if (value == nullptr) {
+    return os << "<none>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  for (int index = 0; int(*value) != 0; ++value, ++index) {
+    os << begl << "[" << index << "]: " << *value;
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVCodec *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "const char *name: " << safe(value->name) << std::endl;
+  os << begl << "const char *long_name: " << safe(value->long_name)
+      << std::endl;
+  os << begl << "AVMediaType type: " << value->type;
+  os << begl << "AVCodecID id: " << value->id;
+  os << begl << "int capabilities: " << value->capabilities << std::endl;
+  os << begl << "AVRational *supported_framerates: "
+      << value->supported_framerates;
+  os << begl << "const int *supported_samplerates: "
+      << value->supported_samplerates;
+  os << begl << "const AVSampleFormat *sample_fmts: " << value->sample_fmts;
+  os << begl << "const uint64_t *channel_layouts: " << value->channel_layouts;
+
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVCodecContext *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "AVMediaType codec_type: " << value->codec_type;
+  os << begl << "const struct AVCodec *codec: " << value->codec;
+  os << begl << "AVCodecID codec_id: " << value->codec_id;
+  os << begl << "int bit_rate: " << value->bit_rate << std::endl;
+  os << begl << "int extradata_size: " << value->extradata_size << std::endl;
+  os << begl << "int width: " << value->width << std::endl;
+  os << begl << "int height: " << value->height << std::endl;
+  os << begl << "int coded_width: " << value->coded_width << std::endl;
+  os << begl << "int coded_height: " << value->coded_height << std::endl;
+  os << begl << "int gop_size: " << value->gop_size << std::endl;
+  os << begl << "int sample_rate: " << value->sample_rate << std::endl;
+  os << begl << "int channels: " << value->channels << std::endl;
+  os << begl << "AVSampleFormat sample_fmt: " << value->sample_fmt;
+  os << begl << "int frame_size: " << value->frame_size << std::endl;
+  os << begl << "int frame_number: " << value->frame_number << std::endl;
+  os << begl << "int block_align: " << value->block_align << std::endl;
+  os << begl << "int cutoff: " << value->cutoff << std::endl;
+  os << begl << "uint64_t channel_layout: " << value->channel_layout
+      << std::endl;
+  os << begl << "uint64_t request_channel_layout: "
+      << value->request_channel_layout << std::endl;
+  os << begl << "AVAudioServiceType audio_service_type: "
+      << value->audio_service_type << std::endl;
+  os << begl << "AVSampleFormat request_sample_fmt: "
+      << value->request_sample_fmt;
+   os << begl << "int profile: " << value->profile << std::endl;
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVRational& value) {
+  return os << value.num << "/" << value.den << std::endl;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVStream *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "int index: " << value->index << std::endl;
+  os << begl << "int id: " << value->id << std::endl;
+  os << begl << "AVCodecContext *codec: " << value->codec;
+  os << begl << "AVRational time_base: " << value->time_base;
+  os << begl << "int64_t start_time: " << value->start_time << std::endl;
+  os << begl << "int64_t duration: " << value->duration << std::endl;
+  os << begl << "int64_t nb_frames: " << value->nb_frames << std::endl;
+  os << begl << "int disposition: " << AV_DISPOSITIONFlags(value->disposition);
+  os << begl << "AVDiscard discard: " << value->discard;
+  os << begl << "AVRational sample_aspect_ratio: "
+      << value->sample_aspect_ratio;
+  os << begl << "AVDictionary *metadata: " << value->metadata;
+  os << begl << "AVRational avg_frame_rate: " << value->avg_frame_rate;
+  os << begl << "AVPacket attached_pic: " << &value->attached_pic;
+  os << begl << "int nb_side_data: " << value->nb_side_data << std::endl;
+  os << begl << "AVPacketSideData side_data: " <<
+      AVPacketSideDataArray(value->side_data, value->nb_side_data);
+  os << begl << "int event_flags: " << AVSTREAM_EVENTFlags(value->event_flags);
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVStreamArray& value) {
+  if (value.items_ == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else if (value.count_ == 0) {
+    return os << "<empty>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  for (unsigned int i = 0; i < value.count_; i++) {
+    os << begl << "[" << i << "] " << value.items_[i];
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, AVFMTFlags value) {
+  if (value.flags_ == 0) {
+    os << "<none>" << std::endl;
+    return os;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  if (value.flags_ & AVFMT_FLAG_GENPTS) {
+    os << begl << "AVFMT_FLAG_GENPTS" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_IGNIDX) {
+    os << begl << "AVFMT_FLAG_IGNIDX" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_NONBLOCK) {
+    os << begl << "AVFMT_FLAG_NONBLOCK" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_IGNDTS) {
+    os << begl << "AVFMT_FLAG_IGNDTS" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_NOFILLIN) {
+    os << begl << "AVFMT_FLAG_NOFILLIN" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_NOPARSE) {
+    os << begl << "AVFMT_FLAG_NOPARSE" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_NOBUFFER) {
+    os << begl << "AVFMT_FLAG_NOBUFFER" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_CUSTOM_IO) {
+    os << begl << "AVFMT_FLAG_CUSTOM_IO" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_DISCARD_CORRUPT) {
+    os << begl << "AVFMT_FLAG_DISCARD_CORRUPT" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_FLUSH_PACKETS) {
+    os << begl << "AVFMT_FLAG_FLUSH_PACKETS" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_BITEXACT) {
+    os << begl << "AVFMT_FLAG_BITEXACT" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_MP4A_LATM) {
+    os << begl << "AVFMT_FLAG_MP4A_LATM" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_SORT_DTS) {
+    os << begl << "AVFMT_FLAG_SORT_DTS" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_PRIV_OPT) {
+    os << begl << "AVFMT_FLAG_PRIV_OPT" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_KEEP_SIDE_DATA) {
+    os << begl << "AVFMT_FLAG_KEEP_SIDE_DATA" << std::endl;
+  }
+  if (value.flags_ & AVFMT_FLAG_FAST_SEEK) {
+    os << begl << "AVFMT_FLAG_FAST_SEEK" << std::endl;
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, AV_DISPOSITIONFlags value) {
+  if (value.flags_ == 0) {
+    os << "<none>" << std::endl;
+    return os;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  if (value.flags_ & AV_DISPOSITION_DEFAULT) {
+    os << begl << "AV_DISPOSITION_DEFAULT  0x0001" << std::endl;
+  }
+  if (value.flags_ & AV_DISPOSITION_DUB) {
+    os << begl << "AV_DISPOSITION_DUB      0x0002" << std::endl;
+  }
+  if (value.flags_ & AV_DISPOSITION_ORIGINAL) {
+    os << begl << "AV_DISPOSITION_ORIGINAL 0x0004" << std::endl;
+  }
+  if (value.flags_ & AV_DISPOSITION_COMMENT) {
+    os << begl << "AV_DISPOSITION_COMMENT  0x0008" << std::endl;
+  }
+  if (value.flags_ & AV_DISPOSITION_LYRICS) {
+    os << begl << "AV_DISPOSITION_LYRICS   0x0010" << std::endl;
+  }
+  if (value.flags_ & AV_DISPOSITION_KARAOKE) {
+    os << begl << "AV_DISPOSITION_KARAOKE  0x0020" << std::endl;
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVBufferRef *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "AVBuffer *buffer: "
+      << (value->buffer == nullptr ? "<nullptr>" : "TODO") << std::endl;
+  os << begl << "uint8_t *data: "
+      << (value->data == nullptr ? "<nullptr>" : "<opaque>") << std::endl;
+  os << begl << "int size: " << value->size << std::endl;
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVFrame *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "uint8_t *data[AV_NUM_DATA_POINTERS]: ";
+  {
+    os << indent;
+    bool any = false;
+    for (int i = 0; i < AV_NUM_DATA_POINTERS; i++) {
+      if (value->data[i] != nullptr) {
+        if (!any) {
+          any = true;
+          os << std::endl;
+        }
+        os << begl << "[" << i << "]: <opaque>" << std::endl;
+      }
+    }
+    if (!any) {
+      os << "<all nullptr>" << std::endl;
+    }
+    os << outdent;
+  }
+
+  os << begl << "int linesize[AV_NUM_DATA_POINTERS]: ";
+  {
+    os << indent;
+    bool any = false;
+    for (int i = 0; i < AV_NUM_DATA_POINTERS; i++) {
+      if (value->linesize[i] != 0) {
+        if (!any) {
+          any = true;
+          os << std::endl;
+        }
+        os << begl << "[" << i << "]: " << value->linesize[i] << std::endl;
+      }
+    }
+    if (!any) {
+      os << "<all zero>" << std::endl;
+    }
+    os << outdent;
+  }
+
+  os << begl << "uint8_t **extended_data: "
+      << (value->extended_data == nullptr ? "<nullptr>" : "<opaque>")
+      << std::endl;
+  os << begl << "int width: " << value->width << std::endl;
+  os << begl << "int height: " << value->height << std::endl;
+  os << begl << "int nb_samples: " << value->nb_samples << std::endl;
+  os << begl << "int format: " << value->format << std::endl;
+  os << begl << "int key_frame: " << value->key_frame << std::endl;
+  os << begl << "int64_t pts: " << value->pts << std::endl;
+  os << begl << "int64_t pkt_pts: " << value->pkt_pts << std::endl;
+  os << begl << "int64_t pkt_dts: " << value->pkt_dts << std::endl;
+  os << begl << "int sample_rate: " << value->sample_rate << std::endl;
+  os << begl << "AVBufferRef *buf[AV_NUM_DATA_POINTERS]: ";
+  {
+    os << indent;
+    bool any = false;
+    for (int i = 0; i < AV_NUM_DATA_POINTERS; i++) {
+      if (value->buf[i] != nullptr) {
+        if (!any) {
+          any = true;
+          os << std::endl;
+        }
+        os << begl << "[" << i << "]:" << value->buf[i];
+      }
+    }
+    if (!any) {
+      os << "<all nullptr>" << std::endl;
+    }
+    os << outdent;
+  }
+  os << begl << "int channels: " << value->channels << std::endl;
+  os << begl << "int pkt_size: " << value->pkt_size << std::endl;
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVPacket *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "AVBufferRef *buf: " << value->buf;
+  os << begl << "int64_t pts: " << value->pts << std::endl;
+  os << begl << "int64_t dts: " << value->dts << std::endl;
+  os << begl << "uint8_t *data: "
+      << (value->data == nullptr ? "<nullptr>" : "<opaque>") << std::endl;
+  os << begl << "int size: " << value->size << std::endl;
+  os << begl << "int stream_index: " << value->stream_index << std::endl;
+  os << begl << "int flags: " << value->flags << std::endl;
+  os << begl << "AVPacketSideData *side_data: " << value->side_data;
+  os << begl << "int side_data_elems: " << value->side_data_elems << std::endl;
+  os << begl << "int duration: " << value->duration << std::endl;
+  os << begl << "int64_t pos: " << value->pos << std::endl;
+  os << begl << "int64_t convergence_duration: "
+      << value->convergence_duration << std::endl;
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVPacketSideData *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    return os << "TODO" << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, const AVPacketSideDataArray& value) {
+  if (value.items_ == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else if (value.count_ == 0) {
+    return os << "<empty>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  for (unsigned int i = 0; i < value.count_; i++) {
+    os << begl << "[" << i << "] " << &value.items_[i];
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVProgram *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    return os << "TODO" << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, const AVProgramArray& value) {
+  if (value.items_ == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else if (value.count_ == 0) {
+    return os << "<empty>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  for (unsigned int i = 0; i < value.count_; i++) {
+    os << begl << "[" << i << "]" << value.items_[i];
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVChapter *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    return os << "TODO" << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, const AVChapterArray& value) {
+  if (value.items_ == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else if (value.count_ == 0) {
+    return os << "<empty>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  for (unsigned int i = 0; i < value.count_; i++) {
+    os << begl << "[" << i << "]" << value.items_[i];
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, AVCodecID value) {
+  return os << avcodec_get_name(value) << " (" << static_cast<int>(value) << ")"
+      << std::endl;
+}
+
+std::ostream& operator<<(std::ostream& os, const AVDictionary *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  }
+  AVDictionaryEntry *entry =
+      av_dict_get(value, "", nullptr, AV_DICT_IGNORE_SUFFIX);
+  if (entry == nullptr) {
+    return os << "<empty>" << std::endl;
+  }
+  os << std::endl;
+
+  os << indent;
+  while (entry != nullptr) {
+    os << begl << safe(entry->key) << ": " << safe(entry->value) << std::endl;
+    entry = av_dict_get(value, "", entry, AV_DICT_IGNORE_SUFFIX);
+  }
+  return os << outdent;
+}
+
+std::ostream& operator<<(std::ostream& os, AVFMT_EVENTFlags value) {
+  if (value.flags_ == 0) {
+    os << "<none>" << std::endl;
+    return os;
+  }
+
+  if (value.flags_ & AVFMT_EVENT_FLAG_METADATA_UPDATED) {
+    return os << "AVFMT_EVENT_FLAG_METADATA_UPDATED" << std::endl;
+  } else {
+    return os << "<UNKNOWN AVFMT_EVENT_FLAG_: " << value.flags_ << ">"
+        << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, AVSTREAM_EVENTFlags value) {
+  if (value.flags_ == 0) {
+    os << "<none>" << std::endl;
+    return os;
+  }
+
+  if (value.flags_ & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
+    return os << "AVSTREAM_EVENT_FLAG_METADATA_UPDATED" << std::endl;
+  } else {
+    return os << "<UNKNOWN AVSTREAM_EVENT_FLAG_: " << value.flags_ << ">"
+        << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, AVFMT_AVOID_NEG_TSFlags value) {
+  switch (value.flags_) {
+    case AVFMT_AVOID_NEG_TS_AUTO:
+      return os << "AVFMT_AVOID_NEG_TS_AUTO" << std::endl;
+    case AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE:
+      return os << "AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE" << std::endl;
+    case AVFMT_AVOID_NEG_TS_MAKE_ZERO:
+      return os << "AVFMT_AVOID_NEG_TS_MAKE_ZERO" << std::endl;
+    default:
+      return os << "<UNKNOWN AVFMT_AVOID_NEG_TS_: " << value.flags_ << ">"
+          << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, AVMediaType value) {
+  switch (value) {
+    case AVMEDIA_TYPE_UNKNOWN:
+      return os << "AVMEDIA_TYPE_UNKNOWN" << std::endl;
+    case AVMEDIA_TYPE_VIDEO:
+      return os << "AVMEDIA_TYPE_VIDEO" << std::endl;
+    case AVMEDIA_TYPE_AUDIO:
+      return os << "AVMEDIA_TYPE_AUDIO" << std::endl;
+    case AVMEDIA_TYPE_DATA:
+      return os << "AVMEDIA_TYPE_DATA" << std::endl;
+    case AVMEDIA_TYPE_SUBTITLE:
+      return os << "AVMEDIA_TYPE_SUBTITLE" << std::endl;
+    case AVMEDIA_TYPE_ATTACHMENT:
+      return os << "AVMEDIA_TYPE_ATTACHMENT" << std::endl;
+    case AVMEDIA_TYPE_NB:
+      return os << "AVMEDIA_TYPE_NB" << std::endl;
+    default:
+      return os << "<UNKNOWN AVMediaType: " << static_cast<int>(value) << ">"
+          << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, AVSampleFormat value) {
+  switch (value) {
+    case AV_SAMPLE_FMT_NONE:
+      return os << "AV_SAMPLE_FMT_NONE" << std::endl;
+    case AV_SAMPLE_FMT_U8:
+      return os << "AV_SAMPLE_FMT_U8" << std::endl;
+    case AV_SAMPLE_FMT_S16:
+      return os << "AV_SAMPLE_FMT_S16" << std::endl;
+    case AV_SAMPLE_FMT_S32:
+      return os << "AV_SAMPLE_FMT_S32" << std::endl;
+    case AV_SAMPLE_FMT_FLT:
+      return os << "AV_SAMPLE_FMT_FLT" << std::endl;
+    case AV_SAMPLE_FMT_DBL:
+      return os << "AV_SAMPLE_FMT_DBL" << std::endl;
+    case AV_SAMPLE_FMT_U8P:
+      return os << "AV_SAMPLE_FMT_U8P" << std::endl;
+    case AV_SAMPLE_FMT_S16P:
+      return os << "AV_SAMPLE_FMT_S16P" << std::endl;
+    case AV_SAMPLE_FMT_S32P:
+      return os << "AV_SAMPLE_FMT_S32P" << std::endl;
+    case AV_SAMPLE_FMT_FLTP:
+      return os << "AV_SAMPLE_FMT_FLTP" << std::endl;
+    case AV_SAMPLE_FMT_DBLP:
+      return os << "AV_SAMPLE_FMT_DBLP" << std::endl;
+    case AV_SAMPLE_FMT_NB:
+      return os << "AV_SAMPLE_FMT_NB" << std::endl;
+    default:
+      return os << "<UNKNOWN AVSampleFormat: " << static_cast<int>(value) << ">"
+          << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, AVColorSpace value) {
+  switch (value) {
+    case AVCOL_SPC_RGB:
+      return os << "AVCOL_SPC_RGB" << std::endl;
+    case AVCOL_SPC_BT709:
+      return os << "AVCOL_SPC_BT709" << std::endl;
+    case AVCOL_SPC_UNSPECIFIED:
+      return os << "AVCOL_SPC_UNSPECIFIED" << std::endl;
+    case AVCOL_SPC_RESERVED:
+      return os << "AVCOL_SPC_RESERVED" << std::endl;
+    case AVCOL_SPC_FCC:
+      return os << "AVCOL_SPC_FCC" << std::endl;
+    case AVCOL_SPC_BT470BG:
+      return os << "AVCOL_SPC_BT470BG" << std::endl;
+    case AVCOL_SPC_SMPTE170M:
+      return os << "AVCOL_SPC_SMPTE170M" << std::endl;
+    case AVCOL_SPC_SMPTE240M:
+      return os << "AVCOL_SPC_SMPTE240M" << std::endl;
+    case AVCOL_SPC_YCOCG:
+      return os << "AVCOL_SPC_YCOCG" << std::endl;
+    case AVCOL_SPC_BT2020_NCL:
+      return os << "AVCOL_SPC_BT2020_NCL" << std::endl;
+    case AVCOL_SPC_BT2020_CL:
+      return os << "AVCOL_SPC_BT2020_CL" << std::endl;
+    case AVCOL_SPC_NB:
+      return os << "AVCOL_SPC_NB" << std::endl;
+    default:
+      return os << "<UNKNOWN AVColorSpace: " << static_cast<int>(value) << ">"
+          << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, enum AVDiscard value) {
+  switch (value) {
+    case AVDISCARD_NONE:
+      return os << "AVDISCARD_NONE" << std::endl;
+    case AVDISCARD_DEFAULT:
+      return os << "AVDISCARD_DEFAULT" << std::endl;
+    case AVDISCARD_NONREF:
+      return os << "AVDISCARD_NONREF" << std::endl;
+    case AVDISCARD_BIDIR:
+      return os << "AVDISCARD_BIDIR" << std::endl;
+    case AVDISCARD_NONINTRA:
+      return os << "AVDISCARD_NONINTRA" << std::endl;
+    case AVDISCARD_NONKEY:
+      return os << "AVDISCARD_NONKEY" << std::endl;
+    case AVDISCARD_ALL:
+      return os << "AVDISCARD_ALL" << std::endl;
+    default:
+      return os << "<UNKNOWN AVDISCARD_: " << static_cast<int>(value) << ">"
+          << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, AVDurationEstimationMethod value) {
+  switch (value) {
+    case AVFMT_DURATION_FROM_PTS:
+      return os << "AVFMT_DURATION_FROM_PTS" << std::endl;
+    case AVFMT_DURATION_FROM_STREAM:
+      return os << "AVFMT_DURATION_FROM_STREAM" << std::endl;
+    case AVFMT_DURATION_FROM_BITRATE:
+      return os << "AVFMT_DURATION_FROM_BITRATE" << std::endl;
+    default:
+      return os << "<UNKNOWN AVDurationEstimationMethod: "
+          << static_cast<int>(value) << ">" << std::endl;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, const AVFormatContext *value) {
+  if (value == nullptr) {
+    return os << "<nullptr>" << std::endl;
+  } else {
+    os << std::endl;
+  }
+
+  os << indent;
+  os << begl << "AVInputFormat *iformat: " << value->iformat;
+  os << begl << "AVOutputFormat *oformat: " << value->oformat;
+  os << begl << "AVIOContext *pb: " << value->pb;
+  os << begl << "int ctx_flags: " << AVFMTCTXFlags(value->ctx_flags);
+  os << begl << "unsigned int nb_streams: " << value->nb_streams << std::endl;
+  os << begl << "AVStream **streams: "
+      << AVStreamArray(value->streams, value->nb_streams);
+  os << begl << "char filename[1024]: " << value->filename << std::endl;
+  os << begl << "int64_t start_time: " << value->start_time << std::endl;
+  os << begl << "int64_t duration: " << value->duration << std::endl;
+  os << begl << "int64_t bit_rate: " << value->bit_rate << std::endl;
+  os << begl << "unsigned int packet_size: " << value->packet_size << std::endl;
+  os << begl << "int max_delay: " << value->max_delay << std::endl;
+  os << begl << "int flags: " << AVFMTFlags(value->flags);
+  os << begl << "int64_t probesize: " << value->probesize << std::endl;
+  os << begl << "unsigned int nb_programs: " << value->nb_programs << std::endl;
+  os << begl << "AVProgram **programs: "
+      << AVProgramArray(value->programs, value->nb_programs);
+  os << begl << "AVCodecID video_codec_id: " << value->video_codec_id;
+  os << begl << "AVCodecID audio_codec_id: " << value->audio_codec_id;
+  os << begl << "AVCodecID subtitle_codec_id: " << value->subtitle_codec_id;
+  os << begl << "unsigned int max_index_size: "
+      << value->max_index_size << std::endl;
+  os << begl << "unsigned int max_picture_buffer: "
+      << value->max_picture_buffer << std::endl;
+  os << begl << "unsigned int nb_chapters: " << value->nb_chapters << std::endl;
+  os << begl << "AVChapter **chapters: "
+      << AVChapterArray(value->chapters, value->nb_chapters);
+  os << begl << "AVDictionary *metadata: " << value->metadata;
+  os << begl << "int64_t start_time_realtime: " << value->start_time_realtime
+      << std::endl;
+  os << begl << "int fps_probe_size: " << value->fps_probe_size << std::endl;
+  os << begl << "int error_recognition: "
+      << value->error_recognition << std::endl;
+  os << begl << "int64_t max_interleave_delta: "
+      << value->max_interleave_delta << std::endl;
+  os << begl << "int strict_std_compliance: "
+      << value->strict_std_compliance << std::endl;
+  os << begl << "int event_flags: " << AVFMT_EVENTFlags(value->flags);
+  os << begl << "int max_ts_probe: " << value->max_ts_probe << std::endl;
+  os << begl << "int avoid_negative_ts: "
+      << AVFMT_AVOID_NEG_TSFlags(value->avoid_negative_ts);
+  os << begl << "int ts_id: " << value->ts_id << std::endl;
+  os << begl << "int audio_preload: " << value->audio_preload << std::endl;
+  os << begl << "int max_chunk_duration: "
+      << value->max_chunk_duration << std::endl;
+  os << begl << "int max_chunk_size: " << value->max_chunk_size << std::endl;
+  os << begl << "int use_wallclock_as_timestamps: "
+      << value->use_wallclock_as_timestamps << std::endl;
+  os << begl << "int avio_flags: " << value->avio_flags << std::endl;
+  os << begl << "AVDurationEstimationMethod duration_estimation_method: "
+      << value->duration_estimation_method;
+  os << begl << "int64_t skip_initial_bytes: " << value->skip_initial_bytes
+      << std::endl;
+  os << begl << "TODO(dalesat): more" << std::endl;
+  return os << outdent;
+}
+
+}  // namespace media
+}  // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_formatting.h b/services/media/framework_ffmpeg/ffmpeg_formatting.h
new file mode 100644
index 0000000..05b1ebe
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_formatting.h
@@ -0,0 +1,116 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_FORMATTING_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_FORMATTING_H_
+
+#include <ostream>
+
+#include "services/media/framework/formatting.h"
+extern "C" {
+#include "third_party/ffmpeg/libavformat/avformat.h"
+}
+
+namespace mojo {
+namespace media {
+
+// See services/media/framework/ostream.h for details.
+
+std::ostream& operator<<(
+    std::ostream& os,
+    const struct AVCodecTag *const *value);
+std::ostream& operator<<(std::ostream& os, const AVInputFormat *value);
+std::ostream& operator<<(std::ostream& os, const AVOutputFormat *value);
+std::ostream& operator<<(std::ostream& os, const AVIOContext *value);
+std::ostream& operator<<(std::ostream& os, const AVCodecContext *value);
+std::ostream& operator<<(std::ostream& os, const AVCodec *value);
+std::ostream& operator<<(std::ostream& os, const AVRational& value);
+std::ostream& operator<<(std::ostream& os, const AVStream *value);
+std::ostream& operator<<(std::ostream& os, const AVBufferRef *value);
+std::ostream& operator<<(std::ostream& os, const AVFrame *value);
+std::ostream& operator<<(std::ostream& os, const AVPacket *value);
+std::ostream& operator<<(std::ostream& os, const AVPacketSideData *value);
+std::ostream& operator<<(std::ostream& os, const AVProgram *value);
+std::ostream& operator<<(std::ostream& os, const AVChapter *value);
+std::ostream& operator<<(std::ostream& os, AVCodecID value);
+std::ostream& operator<<(std::ostream& os, const AVDictionary *value);
+std::ostream& operator<<(std::ostream& os, enum AVDiscard value);
+std::ostream& operator<<(std::ostream& os, AVDurationEstimationMethod value);
+std::ostream& operator<<(std::ostream& os, const AVFormatContext *value);
+std::ostream& operator<<(std::ostream& os, AVMediaType value);
+std::ostream& operator<<(std::ostream& os, AVSampleFormat value);
+std::ostream& operator<<(std::ostream& os, AVColorSpace value);
+
+struct AVPacketSideDataArray {
+  AVPacketSideDataArray(const AVPacketSideData *items, unsigned int count) :
+      items_(items), count_(count) {}
+  const AVPacketSideData *items_;
+  unsigned int count_;
+};
+std::ostream& operator<<(std::ostream& os, const AVPacketSideDataArray& value);
+
+struct AVProgramArray {
+  AVProgramArray(AVProgram **items, unsigned int count) :
+      items_(items), count_(count) {}
+  AVProgram **items_;
+  unsigned int count_;
+};
+std::ostream& operator<<(std::ostream& os, const AVProgramArray& value);
+
+struct AVChapterArray {
+  AVChapterArray(AVChapter **items, unsigned int count) :
+      items_(items), count_(count) {}
+  AVChapter **items_;
+  unsigned int count_;
+};
+std::ostream& operator<<(std::ostream& os, const AVChapterArray& value);
+
+struct AVStreamArray {
+  AVStreamArray(AVStream **items, unsigned int count) :
+      items_(items), count_(count) {}
+  AVStream **items_;
+  unsigned int count_;
+};
+std::ostream& operator<<(std::ostream& os, const AVStreamArray& value);
+
+struct AVFMTFlags {
+  AVFMTFlags(int flags) : flags_(flags) {}
+  int flags_;
+};
+std::ostream& operator<<(std::ostream& os, AVFMTFlags value);
+
+struct AVFMTCTXFlags {
+  AVFMTCTXFlags(int flags) : flags_(flags) {}
+  int flags_;
+};
+std::ostream& operator<<(std::ostream& os, AVFMTCTXFlags value);
+
+struct AV_DISPOSITIONFlags {
+  AV_DISPOSITIONFlags(int flags) : flags_(flags) {}
+  int flags_;
+};
+std::ostream& operator<<(std::ostream& os, AV_DISPOSITIONFlags value);
+
+struct AVFMT_EVENTFlags {
+  AVFMT_EVENTFlags(int flags) : flags_(flags) {}
+  int flags_;
+};
+std::ostream& operator<<(std::ostream& os, AVFMT_EVENTFlags value);
+
+struct AVSTREAM_EVENTFlags {
+  AVSTREAM_EVENTFlags(int flags) : flags_(flags) {}
+  int flags_;
+};
+std::ostream& operator<<(std::ostream& os, AVSTREAM_EVENTFlags value);
+
+struct AVFMT_AVOID_NEG_TSFlags {
+  AVFMT_AVOID_NEG_TSFlags(int flags) : flags_(flags) {}
+  int flags_;
+};
+std::ostream& operator<<(std::ostream& os, AVFMT_AVOID_NEG_TSFlags value);
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_FORMATTING_H_
diff --git a/services/media/framework_ffmpeg/ffmpeg_init.cc b/services/media/framework_ffmpeg/ffmpeg_init.cc
new file mode 100644
index 0000000..4ce9e62
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_init.cc
@@ -0,0 +1,27 @@
+// Copyright 2016 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 "base/synchronization/lock.h"
+#include "services/media/framework_ffmpeg/ffmpeg_init.h"
+
+extern "C" {
+#include "third_party/ffmpeg/libavformat/avformat.h"
+}
+
+namespace mojo {
+namespace media {
+
+void InitFfmpeg() {
+  static base::Lock lock_;
+  static bool initialized_ = false;
+
+  base::AutoLock lock(lock_);
+  if (!initialized_) {
+    initialized_ = true;
+    av_register_all();
+  }
+}
+
+} // namespace media
+} // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_init.h b/services/media/framework_ffmpeg/ffmpeg_init.h
new file mode 100644
index 0000000..7de4223
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_init.h
@@ -0,0 +1,16 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_INIT_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_INIT_H_
+
+namespace mojo {
+namespace media {
+
+void InitFfmpeg();
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_INIT_H_
diff --git a/services/media/framework_ffmpeg/ffmpeg_io.cc b/services/media/framework_ffmpeg/ffmpeg_io.cc
new file mode 100644
index 0000000..c15c614
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_io.cc
@@ -0,0 +1,99 @@
+// Copyright 2016 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 "base/logging.h"
+#include "services/media/framework/parts/reader.h"
+#include "services/media/framework_ffmpeg/ffmpeg_init.h"
+#include "services/media/framework_ffmpeg/ffmpeg_io.h"
+extern "C" {
+#include "third_party/ffmpeg/libavformat/avio.h"
+}
+
+namespace mojo {
+namespace media {
+
+static int AvioRead(void* opaque, uint8_t* buf, int buf_size);
+static int64_t AvioSeek(void* opaque, int64_t offset, int whence);
+
+AvioContextPtr CreateAvioContext(Reader* reader) {
+  // Internal buffer size used by AVIO for reading.
+  const int kBufferSize = 32 * 1024;
+
+  InitFfmpeg();
+
+  AVIOContext* result = avio_alloc_context(
+      static_cast<unsigned char*>(av_malloc(kBufferSize)),
+      kBufferSize,
+      0, // write_flag
+      reader, // opaque
+      &AvioRead,
+      nullptr,
+      &AvioSeek);
+
+  // Ensure FFmpeg only tries to seek when we know how.
+  result->seekable = reader->CanSeek() ? AVIO_SEEKABLE_NORMAL : 0;
+
+  // Ensure writing is disabled.
+  result->write_flag = 0;
+
+  return AvioContextPtr(result);
+}
+
+// Performs a read operation using the signature required for avio.
+static int AvioRead(void* opaque, uint8_t* buf, int buf_size) {
+  Reader* reader = reinterpret_cast<Reader*>(opaque);
+  int result = reader->Read(buf, buf_size);
+  if (result < 0) {
+    result = AVERROR(EIO);
+  }
+  return result;
+}
+
+// Performs a seek operation using the signature required for avio.
+static int64_t AvioSeek(void* opaque, int64_t offset, int whence) {
+  Reader* reader = reinterpret_cast<Reader*>(opaque);
+
+  if (whence == AVSEEK_SIZE) {
+    int64_t result = reader->GetSize();
+    if (result == -1) {
+      return AVERROR(EIO);
+    }
+    return result;
+  }
+
+  int64_t base;
+  switch (whence) {
+    case SEEK_SET:
+      base = 0;
+      break;
+
+    case SEEK_CUR:
+      base = reader->GetPosition();
+      if (base == -1) {
+        return AVERROR(EIO);
+      }
+      break;
+
+    case SEEK_END:
+      base = reader->GetSize();
+      if (base == -1) {
+        return AVERROR(EIO);
+      }
+      break;
+
+    default:
+      NOTREACHED();
+      return AVERROR(EIO);
+  }
+
+  int64_t result = reader->SetPosition(base + offset);
+  if (result == -1) {
+    return AVERROR(EIO);
+  }
+
+  return result;
+}
+
+} // namespace media
+} // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_io.h b/services/media/framework_ffmpeg/ffmpeg_io.h
new file mode 100644
index 0000000..81c1a8c
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_io.h
@@ -0,0 +1,31 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_IO_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_IO_H_
+
+#include "services/media/framework/parts/reader.h"
+extern "C" {
+#include "third_party/ffmpeg/libavformat/avio.h"
+}
+
+namespace mojo {
+namespace media {
+
+struct AVIOContextDeleter {
+  void operator()(AVIOContext* context) const {
+    av_free(context->buffer);
+    av_free(context);
+  }
+};
+
+using AvioContextPtr = std::unique_ptr<AVIOContext, AVIOContextDeleter>;
+
+// Creates an ffmpeg avio_context for a given reader.
+AvioContextPtr CreateAvioContext(Reader* reader);
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_IO_H_
diff --git a/services/media/framework_ffmpeg/ffmpeg_type_converters.cc b/services/media/framework_ffmpeg/ffmpeg_type_converters.cc
new file mode 100644
index 0000000..9b421ec
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_type_converters.cc
@@ -0,0 +1,385 @@
+// Copyright 2016 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 "base/logging.h"
+#include "services/media/framework_ffmpeg/ffmpeg_type_converters.h"
+extern "C" {
+#include "third_party/ffmpeg/libavformat/avformat.h"
+}
+
+// Ffmeg defines this...undefine.
+#undef PixelFormat
+
+namespace mojo {
+namespace media {
+
+namespace {
+
+// Converts an AVSampleFormat into an LpcmStreamType::SampleFormat.
+LpcmStreamType::SampleFormat Convert(AVSampleFormat av_sample_format) {
+  switch (av_sample_format) {
+    case AV_SAMPLE_FMT_U8:
+    case AV_SAMPLE_FMT_U8P:
+      return LpcmStreamType::SampleFormat::kUnsigned8;
+    case AV_SAMPLE_FMT_S16:
+    case AV_SAMPLE_FMT_S16P:
+      return LpcmStreamType::SampleFormat::kSigned16;
+    case AV_SAMPLE_FMT_S32:
+    case AV_SAMPLE_FMT_S32P:
+      return LpcmStreamType::SampleFormat::kSigned24In32;
+    case AV_SAMPLE_FMT_FLT:
+    case AV_SAMPLE_FMT_FLTP:
+      return LpcmStreamType::SampleFormat::kFloat;
+    case AV_SAMPLE_FMT_NONE:
+    case AV_SAMPLE_FMT_DBL:
+    case AV_SAMPLE_FMT_DBLP:
+    case AV_SAMPLE_FMT_NB:
+    default:
+      NOTREACHED() << "unsupported av_sample_format " << av_sample_format;
+      return LpcmStreamType::SampleFormat::kUnknown;
+  }
+}
+
+// Copies a buffer from Bytes into context->extradata. The result is malloc'ed
+// and must be freed.
+void ExtraDataFromBytes(const Bytes& bytes, const AvCodecContextPtr& context) {
+  size_t byte_count = bytes.size();
+  uint8_t* copy = reinterpret_cast<uint8_t*>(malloc(byte_count));
+  std::memcpy(copy, bytes.data(), byte_count);
+  context->extradata = copy;
+  context->extradata_size = byte_count;
+}
+
+// Creates a StreamType from an AVCodecContext describing an LPCM type.
+std::unique_ptr<StreamType> StreamTypeFromLpcmCodecContext(
+    const AVCodecContext& from) {
+  return LpcmStreamType::Create(
+      Convert(from.sample_fmt),
+      from.channels,
+      from.sample_rate);
+}
+
+// Creates a StreamType from an AVCodecContext describing a compressed audio
+// type.
+std::unique_ptr<StreamType>
+StreamTypeFromCompressedAudioCodecContext(const AVCodecContext& from) {
+  CompressedAudioStreamType::AudioEncoding encoding;
+  switch (from.codec_id) {
+    case CODEC_ID_VORBIS:
+      encoding = CompressedAudioStreamType::AudioEncoding::kVorbis;
+      break;
+    default:
+      encoding = CompressedAudioStreamType::AudioEncoding::kUnknown;
+      break;
+  }
+
+  return CompressedAudioStreamType::Create(
+      encoding,
+      Convert(from.sample_fmt),
+      from.channels,
+      from.sample_rate,
+      from.extradata_size == 0 ?
+          nullptr :
+          Bytes::Create(from.extradata, from.extradata_size));
+}
+
+// Converts AVColorSpace and AVColorRange to ColorSpace.
+VideoStreamType::ColorSpace ColorSpaceFromAVColorSpaceAndRange(
+    AVColorSpace color_space,
+    AVColorRange color_range) {
+  // TODO(dalesat): Blindly copied from Chromium.
+  if (color_range == AVCOL_RANGE_JPEG) {
+    return VideoStreamType::ColorSpace::kJpeg;
+  }
+
+  switch (color_space) {
+    case AVCOL_SPC_UNSPECIFIED:
+      return VideoStreamType::ColorSpace::kNotApplicable;
+    case AVCOL_SPC_BT709:
+      return VideoStreamType::ColorSpace::kHdRec709;
+    case AVCOL_SPC_SMPTE170M:
+    case AVCOL_SPC_BT470BG:
+      return VideoStreamType::ColorSpace::kSdRec601;
+    default:
+      return VideoStreamType::ColorSpace::kUnknown;
+  }
+}
+
+// Converts VideoProfile to an ffmpeg profile.
+int FfmpegProfileFromVideoProfile(VideoStreamType::VideoProfile video_profile) {
+  // TODO(dalesat): Blindly copied from Chromium.
+  switch (video_profile) {
+    case VideoStreamType::VideoProfile::kH264Baseline:
+      return FF_PROFILE_H264_BASELINE;
+    case VideoStreamType::VideoProfile::kH264Main:
+      return FF_PROFILE_H264_MAIN;
+    case VideoStreamType::VideoProfile::kH264Extended:
+      return FF_PROFILE_H264_EXTENDED;
+    case VideoStreamType::VideoProfile::kH264High:
+      return FF_PROFILE_H264_HIGH;
+    case VideoStreamType::VideoProfile::kH264High10:
+      return FF_PROFILE_H264_HIGH_10;
+    case VideoStreamType::VideoProfile::kH264High422:
+      return FF_PROFILE_H264_HIGH_422;
+    case VideoStreamType::VideoProfile::kH264High444Predictive:
+      return FF_PROFILE_H264_HIGH_444_PREDICTIVE;
+    case VideoStreamType::VideoProfile::kUnknown:
+    case VideoStreamType::VideoProfile::kNotApplicable:
+    case VideoStreamType::VideoProfile::kH264ScalableBaseline:
+    case VideoStreamType::VideoProfile::kH264ScalableHigh:
+    case VideoStreamType::VideoProfile::kH264StereoHigh:
+    case VideoStreamType::VideoProfile::kH264MultiviewHigh:
+    default:
+      return FF_PROFILE_UNKNOWN;
+   }
+}
+
+// Converts an AVPixelFormat to a PixelFormat.
+VideoStreamType::PixelFormat PixelFormatFromAVPixelFormat(
+    AVPixelFormat av_pixel_format) {
+  // TODO(dalesat): Blindly copied from Chromium.
+  switch (av_pixel_format) {
+    case AV_PIX_FMT_YUV422P:
+    case AV_PIX_FMT_YUVJ422P:
+      return VideoStreamType::PixelFormat::kYv16;
+    case AV_PIX_FMT_YUV444P:
+    case AV_PIX_FMT_YUVJ444P:
+      return VideoStreamType::PixelFormat::kYv24;
+    case AV_PIX_FMT_YUV420P:
+    case AV_PIX_FMT_YUVJ420P:
+      return VideoStreamType::PixelFormat::kYv12;
+    case AV_PIX_FMT_YUVA420P:
+      return VideoStreamType::PixelFormat::kYv12A;
+    default:
+    return VideoStreamType::PixelFormat::kUnknown;
+  }
+}
+
+// Converts a PixelFormat to an AVPixelFormat.
+AVPixelFormat AVPixelFormatFromPixelFormat(
+    VideoStreamType::PixelFormat pixel_format) {
+  // TODO(dalesat): Blindly copied from Chromium.
+  switch (pixel_format) {
+    case VideoStreamType::PixelFormat::kYv12:
+      return AV_PIX_FMT_YUV420P;
+    case VideoStreamType::PixelFormat::kYv16:
+      return AV_PIX_FMT_YUV422P;
+    case VideoStreamType::PixelFormat::kYv12A:
+      return AV_PIX_FMT_YUVA420P;
+    case VideoStreamType::PixelFormat::kYv24:
+      return AV_PIX_FMT_YUV444P;
+    case VideoStreamType::PixelFormat::kUnknown:
+    case VideoStreamType::PixelFormat::kI420:
+    case VideoStreamType::PixelFormat::kNv12:
+    case VideoStreamType::PixelFormat::kNv21:
+    case VideoStreamType::PixelFormat::kUyvy:
+    case VideoStreamType::PixelFormat::kYuy2:
+    case VideoStreamType::PixelFormat::kArgb:
+    case VideoStreamType::PixelFormat::kXrgb:
+    case VideoStreamType::PixelFormat::kRgb24:
+    case VideoStreamType::PixelFormat::kRgb32:
+    case VideoStreamType::PixelFormat::kMjpeg:
+    case VideoStreamType::PixelFormat::kMt21:
+    default:
+      return AV_PIX_FMT_NONE;
+  }
+}
+
+// Creates a StreamType from an AVCodecContext describing a video type.
+std::unique_ptr<StreamType> StreamTypeFromVideoCodecContext(
+    const AVCodecContext& from) {
+  VideoStreamType::VideoEncoding encoding;
+  switch (from.codec_id) {
+    case AV_CODEC_ID_THEORA :
+      encoding = VideoStreamType::VideoEncoding::kTheora;
+      break;
+    case CODEC_ID_VP8:
+      encoding = VideoStreamType::VideoEncoding::kVp8;
+      break;
+    default:
+      encoding = VideoStreamType::VideoEncoding::kUnknown;
+      break;
+  }
+
+  return VideoStreamType::Create(
+      encoding,
+      VideoStreamType::VideoProfile::kNotApplicable,
+      PixelFormatFromAVPixelFormat(from.pix_fmt),
+      ColorSpaceFromAVColorSpaceAndRange(from.colorspace, from.color_range),
+      from.width,
+      from.height,
+      from.coded_width,
+      from.coded_height,
+      from.extradata_size == 0 ?
+          nullptr :
+          Bytes::Create(from.extradata, from.extradata_size));
+}
+
+// Creates a StreamType from an AVCodecContext describing a data type.
+std::unique_ptr<StreamType> StreamTypeFromDataCodecContext(
+    const AVCodecContext& from) {
+  // TODO(dalesat): Implement.
+  return StreamType::Create(StreamType::Scheme::kUnknown);
+}
+
+// Creates a StreamType from an AVCodecContext describing a subtitle type.
+std::unique_ptr<StreamType> StreamTypeFromSubtitleCodecContext(
+    const AVCodecContext& from) {
+  // TODO(dalesat): Implement.
+  return StreamType::Create(StreamType::Scheme::kUnknown);
+}
+
+// Creates an AVCodecContext from LpcmStreamType.
+AvCodecContextPtr CodecContextFromLpcmDetails(
+    const LpcmStreamType& stream_type) {
+  AVCodecID codec_id;
+  AVSampleFormat sample_format;
+
+  switch (stream_type.sample_format()) {
+    case LpcmStreamType::SampleFormat::kUnsigned8:
+      codec_id = AV_CODEC_ID_PCM_U8;
+      sample_format = AV_SAMPLE_FMT_U8;
+      break;
+    case LpcmStreamType::SampleFormat::kSigned16:
+      codec_id = AV_CODEC_ID_PCM_S16LE;
+      sample_format = AV_SAMPLE_FMT_S16;
+      break;
+    case LpcmStreamType::SampleFormat::kSigned24In32:
+      codec_id = AV_CODEC_ID_PCM_S24LE;
+      sample_format = AV_SAMPLE_FMT_S32;
+      break;
+    case LpcmStreamType::SampleFormat::kFloat:
+      codec_id = AV_CODEC_ID_PCM_F32LE;
+      sample_format = AV_SAMPLE_FMT_FLT;
+      break;
+    default:
+      return nullptr;
+  }
+
+  AvCodecContextPtr context(avcodec_alloc_context3(nullptr));
+
+  context->codec_type = AVMEDIA_TYPE_AUDIO;
+  context->codec_id = codec_id;
+  context->sample_fmt = sample_format;
+  context->channels = stream_type.channels();
+  context->sample_rate = stream_type.frames_per_second();
+
+  return context;
+}
+
+// Creates an AVCodecContext from CompressedAudioStreamType.
+AvCodecContextPtr AVCodecContextFromCompressedAudioStreamType(
+    const CompressedAudioStreamType& stream_type) {
+  AVCodecID codec_id = AV_CODEC_ID_NONE;
+  AVSampleFormat sample_format;
+
+  switch (stream_type.encoding()) {
+    case CompressedAudioStreamType::AudioEncoding::kVorbis:
+      codec_id = AV_CODEC_ID_VORBIS;
+      sample_format = AV_SAMPLE_FMT_S16;
+      break;
+    default:
+      return nullptr;
+  }
+
+  if (codec_id == AV_CODEC_ID_NONE) {
+    return nullptr;
+  }
+
+  AvCodecContextPtr context(avcodec_alloc_context3(nullptr));
+
+  context->codec_type = AVMEDIA_TYPE_AUDIO;
+  context->codec_id = codec_id;
+  context->sample_fmt = sample_format;
+  context->channels = stream_type.channels();
+  context->sample_rate = stream_type.frames_per_second();
+
+  if (stream_type.encoding_details()) {
+    ExtraDataFromBytes(*stream_type.encoding_details(), context);
+  }
+
+  return context;
+}
+
+// Creats an AVCodecContext from VideoStreamTypeDetails.
+AvCodecContextPtr AVCodecContextFromVideoStreamType(
+    const VideoStreamType& stream_type) {
+  AVCodecID codec_id = AV_CODEC_ID_NONE;
+
+  // TODO(dalesat): codec_id
+
+  if (codec_id == AV_CODEC_ID_NONE) {
+    return nullptr;
+  }
+
+  AvCodecContextPtr context(avcodec_alloc_context3(nullptr));
+
+  context->codec_type = AVMEDIA_TYPE_VIDEO;
+  context->codec_id = codec_id;
+  context->profile = FfmpegProfileFromVideoProfile(stream_type.profile());
+  context->pix_fmt = AVPixelFormatFromPixelFormat(stream_type.pixel_format());
+  if (stream_type.color_space() == VideoStreamType::ColorSpace::kJpeg) {
+    context->color_range = AVCOL_RANGE_JPEG;
+  }
+  context->coded_width = stream_type.coded_width();
+  context->coded_height = stream_type.coded_height();
+
+  if (stream_type.encoding_details()) {
+    ExtraDataFromBytes(*stream_type.encoding_details(), context);
+  }
+
+  return context;
+}
+
+}  // namespace
+
+std::unique_ptr<StreamType> StreamTypeFromAVCodecContext(
+    const AVCodecContext& from) {
+  switch (from.codec_type) {
+    case AVMEDIA_TYPE_AUDIO:
+      switch (from.codec_id) {
+        case CODEC_ID_PCM_S16BE:
+        case CODEC_ID_PCM_S16LE:
+        case CODEC_ID_PCM_S24BE:
+        case CODEC_ID_PCM_S24LE:
+        case CODEC_ID_PCM_U8:
+          return StreamTypeFromLpcmCodecContext(from);
+        default:
+          if (from.codec == nullptr) {
+            return StreamTypeFromCompressedAudioCodecContext(from);
+          } else {
+            return StreamTypeFromLpcmCodecContext(from);
+          }
+      }
+    case AVMEDIA_TYPE_VIDEO:
+      return StreamTypeFromVideoCodecContext(from);
+    case AVMEDIA_TYPE_UNKNOWN:
+      // Treated as AVMEDIA_TYPE_DATA.
+    case AVMEDIA_TYPE_DATA:
+      return StreamTypeFromDataCodecContext(from);
+    case AVMEDIA_TYPE_SUBTITLE:
+      return StreamTypeFromSubtitleCodecContext(from);
+    case AVMEDIA_TYPE_ATTACHMENT:
+    case AVMEDIA_TYPE_NB:
+    default:
+      return StreamType::Create(StreamType::Scheme::kUnknown);
+  }
+}
+
+AvCodecContextPtr AVCodecContextFromStreamType(const StreamType& stream_type) {
+  switch (stream_type.scheme()) {
+    case StreamType::Scheme::kLpcm:
+      return CodecContextFromLpcmDetails(*stream_type.lpcm());
+    case StreamType::Scheme::kCompressedAudio:
+      return AVCodecContextFromCompressedAudioStreamType(
+          *stream_type.compressed_audio());
+    case StreamType::Scheme::kVideo:
+      return AVCodecContextFromVideoStreamType(*stream_type.video());
+    default:
+      return nullptr;
+  }
+}
+
+} // namespace media
+} // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_type_converters.h b/services/media/framework_ffmpeg/ffmpeg_type_converters.h
new file mode 100644
index 0000000..f20d892
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_type_converters.h
@@ -0,0 +1,39 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_TYPE_CONVERTERS_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_TYPE_CONVERTERS_H_
+
+#include "services/media/framework/stream_type.h"
+extern "C" {
+#include "third_party/ffmpeg/libavformat/avformat.h"
+}
+
+namespace mojo {
+namespace media {
+
+struct AVCodecContextDeleter {
+  void operator()(AVCodecContext* context) const {
+    avcodec_free_context(&context);
+  }
+};
+
+using AvCodecContextPtr =
+    std::unique_ptr<AVCodecContext, AVCodecContextDeleter>;
+
+// We don't specialize TypeConverter here, because specializations have to
+// occur in the same namespace as the original template (mojo::, in this case).
+
+// Creates a MediaType from an AVCodecContext.
+std::unique_ptr<StreamType> StreamTypeFromAVCodecContext(
+    const AVCodecContext& from);
+
+// Creates an AVCodecContext from a StreamType.
+AvCodecContextPtr AVCodecContextFromStreamType(
+    const StreamType& stream_type);
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_TYPE_CONVERTERS_H_
diff --git a/services/media/framework_ffmpeg/ffmpeg_video_decoder.cc b/services/media/framework_ffmpeg/ffmpeg_video_decoder.cc
new file mode 100644
index 0000000..b7a7676
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_video_decoder.cc
@@ -0,0 +1,110 @@
+// Copyright 2016 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 "base/logging.h"
+#include "services/media/framework_ffmpeg/ffmpeg_video_decoder.h"
+
+namespace mojo {
+namespace media {
+
+FfmpegVideoDecoder::FfmpegVideoDecoder(AvCodecContextPtr av_codec_context) :
+    FfmpegDecoderBase(std::move(av_codec_context)) {
+  DCHECK(context());
+}
+
+FfmpegVideoDecoder::~FfmpegVideoDecoder() {}
+
+int FfmpegVideoDecoder::Decode(
+    PayloadAllocator* allocator,
+    bool* frame_decoded_out) {
+  DCHECK(allocator);
+  DCHECK(frame_decoded_out);
+  DCHECK(context());
+  DCHECK(frame());
+
+  int frame_decoded = 0;
+  int input_bytes_used = avcodec_decode_video2(
+    context().get(),
+    frame().get(),
+    &frame_decoded,
+    &packet());
+  *frame_decoded_out = frame_decoded != 0;
+  return input_bytes_used;
+}
+
+PacketPtr FfmpegVideoDecoder::CreateOutputPacket(PayloadAllocator* allocator) {
+  DCHECK(allocator);
+  DCHECK(frame());
+
+  // End of stream is indicated when we're draining and produce no packet.
+  // TODO(dalesat): This is just a copy of the audio version.
+  return Packet::Create(
+      frame()->pts,
+      frame()->pkt_duration,
+      false,
+      packet_size_,
+      frame()->data[0],
+      allocator);
+}
+
+PacketPtr FfmpegVideoDecoder::CreateOutputEndOfStreamPacket() {
+  // TODO(dalesat): Presentation time for this packet.
+  return Packet::CreateEndOfStream(0);
+}
+
+int FfmpegVideoDecoder::AllocateBufferForAvFrame(
+    AVCodecContext* av_codec_context,
+    AVFrame* av_frame,
+    int flags) {
+  // It's important to use av_codec_context here rather than context(),
+  // because av_codec_context is different for different threads when we're
+  // decoding on multiple threads. If this code is moved to an instance method,
+  // be sure to avoid using context().
+
+  // TODO(dalesat): Not sure why/if this is needed.
+  //int result = av_image_check_size(
+  //    av_codec_context->width,
+  //    av_codec_context->height,
+  //    0,
+  //    NULL);
+  //if (result < 0) {
+  //  DCHECK(false) << "av_image_check_size failed";
+  //  return result;
+  //}
+
+  // TODO(dalesat): Not sure why this is needed.
+  int coded_width =
+      std::max(av_codec_context->width, av_codec_context->coded_width);
+  int coded_height =
+      std::max(av_codec_context->height, av_codec_context->coded_height);
+  DCHECK_EQ(coded_width, av_codec_context->coded_width) <<
+      "coded width is less than width";
+  DCHECK_EQ(coded_height, av_codec_context->coded_height) <<
+      "coded height is less than height";
+
+  // TODO(dalesat): Fill in av_frame->data and av_frame->data for each plane.
+
+  av_frame->width = coded_width;
+  av_frame->height = coded_height;
+  av_frame->format = av_codec_context->pix_fmt;
+  av_frame->reordered_opaque = av_codec_context->reordered_opaque;
+
+  av_frame->buf[0] = av_buffer_create(
+      av_frame->data[0], // Because this is the first chunk in the buffer.
+      0, // TODO(dalesat): Provide this.
+      ReleaseBufferForAvFrame,
+      nullptr, // opaque
+      0); // flags
+
+  return 0;
+}
+
+void FfmpegVideoDecoder::ReleaseBufferForAvFrame(
+  void* opaque, uint8_t* buffer) {
+  // Nothing to do.
+  // TODO(dalesat): Can we get rid of this method altogether?
+}
+
+} // namespace media
+} // namespace mojo
diff --git a/services/media/framework_ffmpeg/ffmpeg_video_decoder.h b/services/media/framework_ffmpeg/ffmpeg_video_decoder.h
new file mode 100644
index 0000000..6e3d059
--- /dev/null
+++ b/services/media/framework_ffmpeg/ffmpeg_video_decoder.h
@@ -0,0 +1,51 @@
+// Copyright 2016 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 SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_VIDEO_DECODER_H_
+#define SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_VIDEO_DECODER_H_
+
+#include "services/media/framework_ffmpeg/ffmpeg_decoder_base.h"
+
+namespace mojo {
+namespace media {
+
+// Decoder implementation employing and ffmpeg video decoder.
+// TODO(dalesat): Complete this.
+class FfmpegVideoDecoder : public FfmpegDecoderBase {
+ public:
+  FfmpegVideoDecoder(AvCodecContextPtr av_codec_context);
+
+  ~FfmpegVideoDecoder() override;
+
+ protected:
+  // FfmpegDecoderBase overrides.
+  int Decode(PayloadAllocator* allocator, bool* frame_decoded_out) override;
+
+  PacketPtr CreateOutputPacket(PayloadAllocator* allocator) override;
+
+  PacketPtr CreateOutputEndOfStreamPacket() override;
+
+ private:
+  // Callback used by the ffmpeg decoder to acquire a buffer.
+  static int AllocateBufferForAvFrame(
+      AVCodecContext* av_codec_context,
+      AVFrame* av_frame,
+      int flags);
+
+  // Callback used by the ffmpeg decoder to release a buffer.
+  static void ReleaseBufferForAvFrame(void* opaque, uint8_t* buffer);
+
+  // AllocateBufferForAvFrame deposits the packet size here, because there's
+  // no good evidence of it after avcodec_decode_audio4 completes.
+  uint64_t packet_size_;
+
+  // This is used to verify that an allocated buffer is being used as expected
+  // by ffmpeg avcodec_decode_audio4. AllocateBufferForAvFrame sets it.
+  //void* packet_buffer_;
+};
+
+}  // namespace media
+}  // namespace mojo
+
+#endif // SERVICES_MEDIA_FRAMEWORK_FFMPEG_FFMPEG_VIDEO_DECODER_H_