Commit fe2e8506 authored by johnme@chromium.org's avatar johnme@chromium.org

Revert of Clean up. Experimental user avatars removed. (https://codereview.chromium.org/395133002/)

Reason for revert:
This broke build:
http://build.chromium.org/p/chromium.webkit/builders/Linux%20ChromiumOS%20Tests%20%28dbg%29%282%29/builds/419

With error:
/mnt/data/b/build/slave/Linux_ChromiumOS_Tests__dbg__2_/build/src/out/Debug/app_shell_browsertests: symbol lookup error: /mnt/data/b/build/slave/Linux_ChromiumOS_Tests__dbg__2_/build/src/out/Debug/lib/libmedia.so: undefined symbol: Ebml_Serialize

Original issue's description:
> Clean up. Experimental user avatars removed.
> 
> BUG=387738
> 
> Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=284666

TBR=scherkus@chromium.org,nkostylev@chromium.org,merkulova@chromium.org
NOTREECHECKS=true
NOTRY=true
BUG=387738

Review URL: https://codereview.chromium.org/411523005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284676 0039d316-1c4b-4281-b951-d872f2087c98
parent 49d75aea
...@@ -84,6 +84,11 @@ class User : public user_manager::UserInfo { ...@@ -84,6 +84,11 @@ class User : public user_manager::UserInfo {
const user_manager::UserImage::RawImage& raw_image() const { const user_manager::UserImage::RawImage& raw_image() const {
return user_image_.raw_image(); return user_image_.raw_image();
} }
bool has_animated_image() const { return user_image_.has_animated_image(); }
// Returns raw representation of animated user image.
const user_manager::UserImage::RawImage& animated_image() const {
return user_image_.animated_image();
}
// Whether |raw_image| contains data in format that is considered safe to // Whether |raw_image| contains data in format that is considered safe to
// decode in sensitive environment (on Login screen). // decode in sensitive environment (on Login screen).
......
...@@ -9,6 +9,18 @@ cr.define('options', function() { ...@@ -9,6 +9,18 @@ cr.define('options', function() {
/** @const */ var GridSelectionController = cr.ui.GridSelectionController; /** @const */ var GridSelectionController = cr.ui.GridSelectionController;
/** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
/**
* Number of frames recorded by takeVideo().
* @const
*/
var RECORD_FRAMES = 48;
/**
* FPS at which camera stream is recorded.
* @const
*/
var RECORD_FPS = 16;
/** /**
* Dimensions for camera capture. * Dimensions for camera capture.
* @const * @const
...@@ -478,6 +490,29 @@ cr.define('options', function() { ...@@ -478,6 +490,29 @@ cr.define('options', function() {
return true; return true;
}, },
/**
* Performs video capture from the live camera stream.
* @param {function=} opt_callback Callback that receives taken video as
* data URL of a vertically stacked PNG sprite.
*/
takeVideo: function(opt_callback) {
var canvas = document.createElement('canvas');
canvas.width = CAPTURE_SIZE.width;
canvas.height = CAPTURE_SIZE.height * RECORD_FRAMES;
var ctx = canvas.getContext('2d');
// Force canvas initialization to prevent FPS lag on the first frame.
ctx.fillRect(0, 0, 1, 1);
var captureData = {
callback: opt_callback,
canvas: canvas,
ctx: ctx,
frameNo: 0,
lastTimestamp: new Date().getTime()
};
captureData.timer = window.setInterval(
this.captureVideoFrame_.bind(this, captureData), 1000 / RECORD_FPS);
},
/** /**
* Discard current photo and return to the live camera stream. * Discard current photo and return to the live camera stream.
*/ */
...@@ -534,6 +569,27 @@ cr.define('options', function() { ...@@ -534,6 +569,27 @@ cr.define('options', function() {
return canvas.toDataURL('image/png'); return canvas.toDataURL('image/png');
}, },
/**
* Capture next frame of the video being recorded after a takeVideo() call.
* @param {Object} data Property bag with the recorder details.
* @private
*/
captureVideoFrame_: function(data) {
var lastTimestamp = new Date().getTime();
var delayMs = lastTimestamp - data.lastTimestamp;
console.error('Delay: ' + delayMs + ' (' + (1000 / delayMs + ' FPS)'));
data.lastTimestamp = lastTimestamp;
this.captureFrame_(this.cameraVideo_, data.ctx, CAPTURE_SIZE);
data.ctx.translate(0, CAPTURE_SIZE.height);
if (++data.frameNo == RECORD_FRAMES) {
window.clearTimeout(data.timer);
if (data.callback && typeof data.callback == 'function')
data.callback(data.canvas.toDataURL('image/png'));
}
},
/** /**
* Adds new image to the user image grid. * Adds new image to the user image grid.
* @param {string} src Image URL. * @param {string} src Image URL.
......
...@@ -19,15 +19,31 @@ ...@@ -19,15 +19,31 @@
namespace { namespace {
// Animated key is used in user image URL requests to specify that
// animated version of user image is required. Without that key
// non-animated version of user image should be returned.
const char kKeyAnimated[] = "animated";
// Parses the user image URL, which looks like // Parses the user image URL, which looks like
// "chrome://userimage/user@host?key1=value1&...&key_n=value_n", // "chrome://userimage/user@host?key1=value1&...&key_n=value_n",
// to user email. // to user email and optional parameters.
void ParseRequest(const GURL& url, void ParseRequest(const GURL& url,
std::string* email) { std::string* email,
bool* is_image_animated) {
DCHECK(url.is_valid()); DCHECK(url.is_valid());
*email = net::UnescapeURLComponent(url.path().substr(1), *email = net::UnescapeURLComponent(url.path().substr(1),
(net::UnescapeRule::URL_SPECIAL_CHARS | (net::UnescapeRule::URL_SPECIAL_CHARS |
net::UnescapeRule::SPACES)); net::UnescapeRule::SPACES));
std::string url_spec = url.possibly_invalid_spec();
url::Component query = url.parsed_for_possibly_invalid_spec().query;
url::Component key, value;
*is_image_animated = false;
while (ExtractQueryKeyValue(url_spec.c_str(), &query, &key, &value)) {
if (url_spec.substr(key.begin, key.len) == kKeyAnimated) {
*is_image_animated = true;
break;
}
}
} }
} // namespace } // namespace
...@@ -37,10 +53,13 @@ namespace options { ...@@ -37,10 +53,13 @@ namespace options {
base::RefCountedMemory* UserImageSource::GetUserImage( base::RefCountedMemory* UserImageSource::GetUserImage(
const std::string& email, const std::string& email,
bool is_image_animated,
ui::ScaleFactor scale_factor) const { ui::ScaleFactor scale_factor) const {
const chromeos::User* user = chromeos::UserManager::Get()->FindUser(email); const chromeos::User* user = chromeos::UserManager::Get()->FindUser(email);
if (user) { if (user) {
if (user->has_raw_image()) { if (user->has_animated_image() && is_image_animated) {
return new base::RefCountedBytes(user->animated_image());
} else if (user->has_raw_image()) {
return new base::RefCountedBytes(user->raw_image()); return new base::RefCountedBytes(user->raw_image());
} else if (user->image_is_stub()) { } else if (user->image_is_stub()) {
return ResourceBundle::GetSharedInstance(). return ResourceBundle::GetSharedInstance().
...@@ -74,14 +93,26 @@ void UserImageSource::StartDataRequest( ...@@ -74,14 +93,26 @@ void UserImageSource::StartDataRequest(
int render_frame_id, int render_frame_id,
const content::URLDataSource::GotDataCallback& callback) { const content::URLDataSource::GotDataCallback& callback) {
std::string email; std::string email;
bool is_image_animated = false;
GURL url(chrome::kChromeUIUserImageURL + path); GURL url(chrome::kChromeUIUserImageURL + path);
ParseRequest(url, &email); ParseRequest(url, &email, &is_image_animated);
callback.Run(GetUserImage(email, ui::SCALE_FACTOR_100P)); callback.Run(GetUserImage(email, is_image_animated, ui::SCALE_FACTOR_100P));
} }
std::string UserImageSource::GetMimeType(const std::string& path) const { std::string UserImageSource::GetMimeType(const std::string& path) const {
// We need to explicitly return a mime type, otherwise if the user tries to // We need to explicitly return a mime type, otherwise if the user tries to
// drag the image they get no extension. // drag the image they get no extension.
std::string email;
bool is_image_animated = false;
GURL url(chrome::kChromeUIUserImageURL + path);
ParseRequest(url, &email, &is_image_animated);
if (is_image_animated) {
const chromeos::User* user = chromeos::UserManager::Get()->FindUser(email);
if (user && user->has_animated_image())
return "image/gif";
}
return "image/png"; return "image/png";
} }
......
...@@ -35,9 +35,11 @@ class UserImageSource : public content::URLDataSource { ...@@ -35,9 +35,11 @@ class UserImageSource : public content::URLDataSource {
const content::URLDataSource::GotDataCallback& callback) OVERRIDE; const content::URLDataSource::GotDataCallback& callback) OVERRIDE;
virtual std::string GetMimeType(const std::string& path) const OVERRIDE; virtual std::string GetMimeType(const std::string& path) const OVERRIDE;
// Returns PNG encoded image for user with specified email. If there's // Returns PNG or GIF (when possible and if |is_image_animated| flag
// is true) encoded image for user with specified email. If there's
// no user with such email, returns the first default image. // no user with such email, returns the first default image.
base::RefCountedMemory* GetUserImage(const std::string& email, base::RefCountedMemory* GetUserImage(const std::string& email,
bool is_image_animated,
ui::ScaleFactor scale_factor) const; ui::ScaleFactor scale_factor) const;
private: private:
......
...@@ -15,6 +15,17 @@ namespace { ...@@ -15,6 +15,17 @@ namespace {
// Default quality for encoding user images. // Default quality for encoding user images.
const int kDefaultEncodingQuality = 90; const int kDefaultEncodingQuality = 90;
bool IsAnimatedImage(const UserImage::RawImage& data) {
const char kGIFStamp[] = "GIF";
const size_t kGIFStampLength = sizeof(kGIFStamp) - 1;
if (data.size() >= kGIFStampLength &&
memcmp(&data[0], kGIFStamp, kGIFStampLength) == 0) {
return true;
}
return false;
}
bool EncodeImageSkia(const gfx::ImageSkia& image, bool EncodeImageSkia(const gfx::ImageSkia& image,
std::vector<unsigned char>* output) { std::vector<unsigned char>* output) {
TRACE_EVENT2("oobe", "EncodeImageSkia", TRACE_EVENT2("oobe", "EncodeImageSkia",
...@@ -47,12 +58,14 @@ UserImage UserImage::CreateAndEncode(const gfx::ImageSkia& image) { ...@@ -47,12 +58,14 @@ UserImage UserImage::CreateAndEncode(const gfx::ImageSkia& image) {
UserImage::UserImage() UserImage::UserImage()
: has_raw_image_(false), : has_raw_image_(false),
has_animated_image_(false),
is_safe_format_(false) { is_safe_format_(false) {
} }
UserImage::UserImage(const gfx::ImageSkia& image) UserImage::UserImage(const gfx::ImageSkia& image)
: image_(image), : image_(image),
has_raw_image_(false), has_raw_image_(false),
has_animated_image_(false),
is_safe_format_(false) { is_safe_format_(false) {
} }
...@@ -60,9 +73,19 @@ UserImage::UserImage(const gfx::ImageSkia& image, ...@@ -60,9 +73,19 @@ UserImage::UserImage(const gfx::ImageSkia& image,
const RawImage& raw_image) const RawImage& raw_image)
: image_(image), : image_(image),
has_raw_image_(false), has_raw_image_(false),
has_animated_image_(false),
is_safe_format_(false) { is_safe_format_(false) {
if (IsAnimatedImage(raw_image)) {
has_animated_image_ = true;
animated_image_ = raw_image;
if (EncodeImageSkia(image_, &raw_image_)) {
has_raw_image_ = true;
MarkAsSafe();
}
} else {
has_raw_image_ = true; has_raw_image_ = true;
raw_image_ = raw_image; raw_image_ = raw_image;
}
} }
UserImage::~UserImage() {} UserImage::~UserImage() {}
......
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
namespace user_manager { namespace user_manager {
// Wrapper class storing a still image and it's raw representation. Could be // Wrapper class storing a still image and it's raw representation. Could be
// used for storing profile images and user wallpapers. // used for storing profile images (including animated profile images) and user
// wallpapers.
class USER_MANAGER_EXPORT UserImage { class USER_MANAGER_EXPORT UserImage {
public: public:
// TODO(ivankr): replace with RefCountedMemory to prevent copying. // TODO(ivankr): replace with RefCountedMemory to prevent copying.
...@@ -33,6 +34,9 @@ class USER_MANAGER_EXPORT UserImage { ...@@ -33,6 +34,9 @@ class USER_MANAGER_EXPORT UserImage {
explicit UserImage(const gfx::ImageSkia& image); explicit UserImage(const gfx::ImageSkia& image);
// Creates a new instance from a given still frame and raw representation. // Creates a new instance from a given still frame and raw representation.
// |raw_image| can be animated, in which case animated_image() will return the
// original |raw_image| and raw_image() will return the encoded representation
// of |image|.
UserImage(const gfx::ImageSkia& image, const RawImage& raw_image); UserImage(const gfx::ImageSkia& image, const RawImage& raw_image);
virtual ~UserImage(); virtual ~UserImage();
...@@ -46,6 +50,10 @@ class USER_MANAGER_EXPORT UserImage { ...@@ -46,6 +50,10 @@ class USER_MANAGER_EXPORT UserImage {
// Discards the stored raw image, freeing used memory. // Discards the stored raw image, freeing used memory.
void DiscardRawImage(); void DiscardRawImage();
// Optional raw representation of the animated image.
bool has_animated_image() const { return has_animated_image_; }
const RawImage& animated_image() const { return animated_image_; }
// URL from which this image was originally downloaded, if any. // URL from which this image was originally downloaded, if any.
void set_url(const GURL& url) { url_ = url; } void set_url(const GURL& url) { url_ = url; }
GURL url() const { return url_; } GURL url() const { return url_; }
...@@ -62,6 +70,8 @@ class USER_MANAGER_EXPORT UserImage { ...@@ -62,6 +70,8 @@ class USER_MANAGER_EXPORT UserImage {
gfx::ImageSkia image_; gfx::ImageSkia image_;
bool has_raw_image_; bool has_raw_image_;
RawImage raw_image_; RawImage raw_image_;
bool has_animated_image_;
RawImage animated_image_;
GURL url_; GURL url_;
// If image was loaded from the local file, file path is stored here. // If image was loaded from the local file, file path is stored here.
......
...@@ -450,6 +450,27 @@ component("media") { ...@@ -450,6 +450,27 @@ component("media") {
} }
} }
if (is_chromeos) {
# A simple WebM encoder for animated avatars on ChromeOS.
sources += [
"formats/webm/chromeos/ebml_writer.cc",
"formats/webm/chromeos/ebml_writer.h",
"formats/webm/chromeos/webm_encoder.cc",
"formats/webm/chromeos/webm_encoder.h",
]
deps += [
"//third_party/libvpx",
"//third_party/libyuv"
]
# For VaapiVideoEncodeAccelerator.
if (cpu_arch != "arm" && use_x11) {
sources += [
"filters/h264_bitstream_buffer.cc",
"filters/h264_bitstream_buffer.h",
]
}
}
if (!is_ios) { if (!is_ios) {
deps += [ "//third_party/libyuv" ] deps += [ "//third_party/libyuv" ]
} }
......
include_rules = [
"+libyuv",
"+third_party/libvpx",
]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/webm/chromeos/ebml_writer.h"
#include "media/base/media_export.h"
extern "C" {
#include "third_party/libvpx/source/libvpx/third_party/libmkv/EbmlWriter.h"
EbmlGlobal::EbmlGlobal() {
}
EbmlGlobal::~EbmlGlobal() {
}
// These functions must be in the global namespace and visible to libmkv.
void MEDIA_EXPORT Ebml_Write(EbmlGlobal* glob,
const void* buffer,
unsigned long len) {
glob->write_cb.Run(buffer, len);
}
void MEDIA_EXPORT Ebml_Serialize(EbmlGlobal* glob,
const void* buffer,
int buffer_size,
unsigned long len) {
glob->serialize_cb.Run(buffer, buffer_size, len);
}
} // extern "C"
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_FORMATS_WEBM_CHROMEOS_EBML_WRITER_H_
#define MEDIA_FORMATS_WEBM_CHROMEOS_EBML_WRITER_H_
#include "base/callback.h"
// This struct serves as a bridge betweeen static libmkv interface and Chrome's
// base::Callback. Must be in the global namespace. See EbmlWriter.h.
struct EbmlGlobal {
EbmlGlobal();
~EbmlGlobal();
base::Callback<void(const void* buffer, unsigned long len)> write_cb;
base::Callback<void(const void* buffer, int buffer_size, unsigned long len)>
serialize_cb;
};
#endif // MEDIA_FORMATS_WEBM_CHROMEOS_EBML_WRITER_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/webm/chromeos/webm_encoder.h"
#include "base/bind.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "libyuv/convert.h"
#include "libyuv/video_common.h"
#include "third_party/skia/include/core/SkBitmap.h"
extern "C" {
// Getting the right degree of C compatibility has been a constant struggle.
// - Stroustrup, C++ Report, 12(7), July/August 2000.
#define private priv
#include "third_party/libvpx/source/libvpx/third_party/libmkv/EbmlIDs.h"
#include "third_party/libvpx/source/libvpx/third_party/libmkv/EbmlWriter.h"
#undef private
}
// Number of encoder threads to use.
static const int kNumEncoderThreads = 2;
// Need a fixed size serializer for the track ID. libmkv provides a 64 bit
// one, but not a 32 bit one.
static void Ebml_SerializeUnsigned32(EbmlGlobal* ebml,
unsigned long class_id,
uint64_t value) {
uint8 size_serialized = 4 | 0x80;
Ebml_WriteID(ebml, class_id);
Ebml_Serialize(ebml, &size_serialized, sizeof(size_serialized), 1);
Ebml_Serialize(ebml, &value, sizeof(value), 4);
}
// Wrapper functor for vpx_codec_destroy().
struct VpxCodecDeleter {
void operator()(vpx_codec_ctx_t* codec) {
vpx_codec_destroy(codec);
}
};
// Wrapper functor for vpx_img_free().
struct VpxImgDeleter {
void operator()(vpx_image_t* image) {
vpx_img_free(image);
}
};
namespace media {
namespace chromeos {
WebmEncoder::WebmEncoder(const base::FilePath& output_path,
int bitrate,
bool realtime)
: bitrate_(bitrate),
deadline_(realtime ? VPX_DL_REALTIME : VPX_DL_GOOD_QUALITY),
output_path_(output_path),
has_errors_(false) {
ebml_writer_.write_cb = base::Bind(
&WebmEncoder::EbmlWrite, base::Unretained(this));
ebml_writer_.serialize_cb = base::Bind(
&WebmEncoder::EbmlSerialize, base::Unretained(this));
}
WebmEncoder::~WebmEncoder() {
}
bool WebmEncoder::EncodeFromSprite(const SkBitmap& sprite,
int fps_n,
int fps_d) {
DCHECK(!sprite.isNull());
DCHECK(!sprite.empty());
has_errors_ = false;
width_ = sprite.width();
height_ = sprite.width();
fps_.num = fps_n;
fps_.den = fps_d;
// Sprite is tiled vertically.
frame_count_ = sprite.height() / width_;
vpx_image_t image;
vpx_img_alloc(&image, VPX_IMG_FMT_I420, width_, height_, 16);
// Ensure that image is freed after return.
scoped_ptr<vpx_image_t, VpxImgDeleter> image_ptr(&image);
const vpx_codec_iface_t* codec_iface = vpx_codec_vp8_cx();
DCHECK(codec_iface);
vpx_codec_err_t ret = vpx_codec_enc_config_default(codec_iface, &config_, 0);
DCHECK_EQ(VPX_CODEC_OK, ret);
config_.rc_target_bitrate = bitrate_;
config_.g_w = width_;
config_.g_h = height_;
config_.g_pass = VPX_RC_ONE_PASS;
config_.g_profile = 0; // Default profile.
config_.g_threads = kNumEncoderThreads;
config_.rc_min_quantizer = 0;
config_.rc_max_quantizer = 63; // Maximum possible range.
config_.g_timebase.num = fps_.den;
config_.g_timebase.den = fps_.num;
config_.kf_mode = VPX_KF_AUTO; // Auto key frames.
vpx_codec_ctx_t codec;
ret = vpx_codec_enc_init(&codec, codec_iface, &config_, 0);
if (ret != VPX_CODEC_OK)
return false;
// Ensure that codec context is freed after return.
scoped_ptr<vpx_codec_ctx_t, VpxCodecDeleter> codec_ptr(&codec);
SkAutoLockPixels lock_sprite(sprite);
const uint8* src = reinterpret_cast<const uint8*>(sprite.getAddr32(0, 0));
size_t src_frame_size = sprite.getSize();
int crop_y = 0;
if (!WriteWebmHeader())
return false;
for (size_t frame = 0; frame < frame_count_ && !has_errors_; ++frame) {
int res = libyuv::ConvertToI420(
src, src_frame_size,
image.planes[VPX_PLANE_Y], image.stride[VPX_PLANE_Y],
image.planes[VPX_PLANE_U], image.stride[VPX_PLANE_U],
image.planes[VPX_PLANE_V], image.stride[VPX_PLANE_V],
0, crop_y, // src origin
width_, sprite.height(), // src size
width_, height_, // dest size
libyuv::kRotate0,
libyuv::FOURCC_ARGB);
if (res) {
has_errors_ = true;
break;
}
crop_y += height_;
ret = vpx_codec_encode(&codec, &image, frame, 1, 0, deadline_);
if (ret != VPX_CODEC_OK) {
has_errors_ = true;
break;
}
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t* packet;
while (!has_errors_ && (packet = vpx_codec_get_cx_data(&codec, &iter))) {
if (packet->kind == VPX_CODEC_CX_FRAME_PKT)
WriteWebmBlock(packet);
}
}
return WriteWebmFooter();
}
bool WebmEncoder::WriteWebmHeader() {
output_ = base::OpenFile(output_path_, "wb");
if (!output_)
return false;
// Global header.
StartSubElement(EBML);
{
Ebml_SerializeUnsigned(&ebml_writer_, EBMLVersion, 1);
Ebml_SerializeUnsigned(&ebml_writer_, EBMLReadVersion, 1);
Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxIDLength, 4);
Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxSizeLength, 8);
Ebml_SerializeString(&ebml_writer_, DocType, "webm");
Ebml_SerializeUnsigned(&ebml_writer_, DocTypeVersion, 2);
Ebml_SerializeUnsigned(&ebml_writer_, DocTypeReadVersion, 2);
}
EndSubElement(); // EBML
// Single segment with a video track.
StartSubElement(Segment);
{
StartSubElement(Info);
{
// All timecodes in the segment will be expressed in milliseconds.
Ebml_SerializeUnsigned(&ebml_writer_, TimecodeScale, 1000000);
double duration = 1000. * frame_count_ * fps_.den / fps_.num;
Ebml_SerializeFloat(&ebml_writer_, Segment_Duration, duration);
}
EndSubElement(); // Info
StartSubElement(Tracks);
{
StartSubElement(TrackEntry);
{
Ebml_SerializeUnsigned(&ebml_writer_, TrackNumber, 1);
Ebml_SerializeUnsigned32(&ebml_writer_, TrackUID, 1);
Ebml_SerializeUnsigned(&ebml_writer_, TrackType, 1); // Video
Ebml_SerializeString(&ebml_writer_, CodecID, "V_VP8");
StartSubElement(Video);
{
Ebml_SerializeUnsigned(&ebml_writer_, PixelWidth, width_);
Ebml_SerializeUnsigned(&ebml_writer_, PixelHeight, height_);
Ebml_SerializeUnsigned(&ebml_writer_, StereoMode, 0); // Mono
float fps = static_cast<float>(fps_.num) / fps_.den;
Ebml_SerializeFloat(&ebml_writer_, FrameRate, fps);
}
EndSubElement(); // Video
}
EndSubElement(); // TrackEntry
}
EndSubElement(); // Tracks
StartSubElement(Cluster); {
Ebml_SerializeUnsigned(&ebml_writer_, Timecode, 0);
} // Cluster left open.
} // Segment left open.
// No check for |has_errors_| here because |false| is only returned when
// opening file fails.
return true;
}
void WebmEncoder::WriteWebmBlock(const vpx_codec_cx_pkt_t* packet) {
bool is_keyframe = packet->data.frame.flags & VPX_FRAME_IS_KEY;
int64_t pts_ms = 1000 * packet->data.frame.pts * fps_.den / fps_.num;
DVLOG(1) << "Video packet @" << pts_ms << " ms "
<< packet->data.frame.sz << " bytes "
<< (is_keyframe ? "K" : "");
Ebml_WriteID(&ebml_writer_, SimpleBlock);
uint32 block_length = (packet->data.frame.sz + 4) | 0x10000000;
EbmlSerializeHelper(&block_length, 4);
uint8 track_number = 1 | 0x80;
EbmlSerializeHelper(&track_number, 1);
EbmlSerializeHelper(&pts_ms, 2);
uint8 flags = 0;
if (is_keyframe)
flags |= 0x80;
if (packet->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
flags |= 0x08;
EbmlSerializeHelper(&flags, 1);
EbmlWrite(packet->data.frame.buf, packet->data.frame.sz);
}
bool WebmEncoder::WriteWebmFooter() {
EndSubElement(); // Cluster
EndSubElement(); // Segment
DCHECK(ebml_sub_elements_.empty());
return base::CloseFile(output_) && !has_errors_;
}
void WebmEncoder::StartSubElement(unsigned long class_id) {
Ebml_WriteID(&ebml_writer_, class_id);
ebml_sub_elements_.push(ftell(output_));
static const uint64_t kUnknownLen = 0x01FFFFFFFFFFFFFFLLU;
EbmlSerializeHelper(&kUnknownLen, 8);
}
void WebmEncoder::EndSubElement() {
DCHECK(!ebml_sub_elements_.empty());
long int end_pos = ftell(output_);
long int start_pos = ebml_sub_elements_.top();
ebml_sub_elements_.pop();
uint64_t size = (end_pos - start_pos - 8) | 0x0100000000000000ULL;
// Seek to the beginning of the sub-element and patch in the calculated size.
if (fseek(output_, start_pos, SEEK_SET)) {
has_errors_ = true;
LOG(ERROR) << "Error writing to " << output_path_.value();
}
EbmlSerializeHelper(&size, 8);
// Restore write position.
if (fseek(output_, end_pos, SEEK_SET)) {
has_errors_ = true;
LOG(ERROR) << "Error writing to " << output_path_.value();
}
}
void WebmEncoder::EbmlWrite(const void* buffer,
unsigned long len) {
if (fwrite(buffer, 1, len, output_) != len) {
has_errors_ = true;
LOG(ERROR) << "Error writing to " << output_path_.value();
}
}
template <class T>
void WebmEncoder::EbmlSerializeHelper(const T* buffer, unsigned long len) {
for (int i = len - 1; i >= 0; i--) {
uint8 c = *buffer >> (i * CHAR_BIT);
EbmlWrite(&c, 1);
}
}
void WebmEncoder::EbmlSerialize(const void* buffer,
int buffer_size,
unsigned long len) {
switch (buffer_size) {
case 1:
return EbmlSerializeHelper(static_cast<const int8_t*>(buffer), len);
case 2:
return EbmlSerializeHelper(static_cast<const int16_t*>(buffer), len);
case 4:
return EbmlSerializeHelper(static_cast<const int32_t*>(buffer), len);
case 8:
return EbmlSerializeHelper(static_cast<const int64_t*>(buffer), len);
default:
NOTREACHED() << "Invalid EbmlSerialize length: " << len;
}
}
} // namespace chromeos
} // namespace media
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_FORMATS_WEBM_CHROMEOS_WEBM_ENCODER_H_
#define MEDIA_FORMATS_WEBM_CHROMEOS_WEBM_ENCODER_H_
#include <stdio.h>
#include <stack>
#include "base/files/file_path.h"
#include "media/base/media_export.h"
#include "media/formats/webm/chromeos/ebml_writer.h"
extern "C" {
#define VPX_CODEC_DISABLE_COMPAT 1
#include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h"
#include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
}
class SkBitmap;
namespace base {
class FilePath;
}
namespace media {
namespace chromeos {
// WebM encoder using libvpx. Currently only supports one-pass, constant bitrate
// encoding of short files consisting of a single video track. Seek info and
// cues are not supported, so generated .webm file does not strictly adhere to
// WebM standard (http://www.webmproject.org/code/specs/container/).
class MEDIA_EXPORT WebmEncoder {
public:
// Create new instance for writing to |output_path|. If |realtime| is |true|,
// uses realtime deadline, otherwise - "good quality" deadline.
WebmEncoder(const base::FilePath& output_path, int bitrate, bool realtime);
~WebmEncoder();
// Encodes video from a Nx(N*M) sprite, having M frames of size NxN with FPS
// |fps_n/fps_d|. Must be called on a thread that allows disk IO.
// Returns |true| iff encoding and writing to file is successful.
bool EncodeFromSprite(const SkBitmap& sprite, int fps_n, int fps_d);
private:
// Writes global WebM header and starts a single video track. Returns |false|
// if there was an error opening file for writing.
bool WriteWebmHeader();
// Writes VPX packet to output file.
void WriteWebmBlock(const vpx_codec_cx_pkt_t* packet);
// Finishes video track and closes output file. Returns |false| if there were
// any error during encoding/writing file.
bool WriteWebmFooter();
// Starts a new WebM sub-element of given type. Those can be nested.
void StartSubElement(unsigned long class_id);
// Closes current top-level sub-element.
void EndSubElement();
// libmkv callbacks.
void EbmlWrite(const void* buffer, unsigned long len);
void EbmlSerialize(const void* buffer, int buffer_size, unsigned long len);
template <typename T>
void EbmlSerializeHelper(const T* buffer, unsigned long len);
// Video dimensions and FPS.
size_t width_;
size_t height_;
vpx_rational_t fps_;
// Number of frames in video.
size_t frame_count_;
// VPX config in use.
vpx_codec_enc_cfg_t config_;
// VPX parameters.
int bitrate_;
unsigned long deadline_;
// EbmlWriter context.
EbmlGlobal ebml_writer_;
// Stack with start offsets of currently open sub-elements.
std::stack<long int> ebml_sub_elements_;
base::FilePath output_path_;
FILE* output_;
// True if an error occured while encoding/writing to file.
bool has_errors_;
DISALLOW_COPY_AND_ASSIGN(WebmEncoder);
};
} // namespace chromeos
} // namespace media
#endif // MEDIA_FORMATS_WEBM_CHROMEOS_WEBM_ENCODER_H_
...@@ -650,6 +650,19 @@ ...@@ -650,6 +650,19 @@
'DISABLE_USER_INPUT_MONITOR', 'DISABLE_USER_INPUT_MONITOR',
], ],
}], }],
# A simple WebM encoder for animated avatars on ChromeOS.
['chromeos==1', {
'dependencies': [
'../third_party/libvpx/libvpx.gyp:libvpx',
'../third_party/libyuv/libyuv.gyp:libyuv',
],
'sources': [
'formats/webm/chromeos/ebml_writer.cc',
'formats/webm/chromeos/ebml_writer.h',
'formats/webm/chromeos/webm_encoder.cc',
'formats/webm/chromeos/webm_encoder.h',
],
}],
# For VaapiVideoEncodeAccelerator. # For VaapiVideoEncodeAccelerator.
['target_arch != "arm" and chromeos == 1 and use_x11 == 1', { ['target_arch != "arm" and chromeos == 1 and use_x11 == 1', {
'sources': [ 'sources': [
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment