Index: ppapi/examples/video_encode/video_encode.cc |
diff --git a/ppapi/examples/video_encode/video_encode.cc b/ppapi/examples/video_encode/video_encode.cc |
index 26c66dff1fa34d7a8d30bf9619455843ffd17049..66d141a525648ef1551707440e20909ed560e721 100644 |
--- a/ppapi/examples/video_encode/video_encode.cc |
+++ b/ppapi/examples/video_encode/video_encode.cc |
@@ -7,6 +7,7 @@ |
#include <string.h> |
#include <iostream> |
+#include <map> |
#include <sstream> |
#include <vector> |
@@ -24,9 +25,6 @@ |
#include "ppapi/cpp/video_frame.h" |
#include "ppapi/utility/completion_callback_factory.h" |
-// TODO(llandwerlin): turn on by default when we have software encode. |
-// #define USE_VP8_INSTEAD_OF_H264 |
- |
// When compiling natively on Windows, PostMessage can be #define-d to |
// something else. |
#ifdef PostMessage |
@@ -39,52 +37,66 @@ |
#undef NDEBUG |
#include <assert.h> |
+#define fourcc(a, b, c, d) \ |
+ (((uint32_t)(a) << 0) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | \ |
+ ((uint32_t)(d) << 24)) |
+ |
namespace { |
-std::string VideoProfileToString(PP_VideoProfile profile) { |
- switch (profile) { |
- case PP_VIDEOPROFILE_H264BASELINE: |
- return "h264baseline"; |
- case PP_VIDEOPROFILE_H264MAIN: |
- return "h264main"; |
- case PP_VIDEOPROFILE_H264EXTENDED: |
- return "h264extended"; |
- case PP_VIDEOPROFILE_H264HIGH: |
- return "h264high"; |
- case PP_VIDEOPROFILE_H264HIGH10PROFILE: |
- return "h264high10"; |
- case PP_VIDEOPROFILE_H264HIGH422PROFILE: |
- return "h264high422"; |
- case PP_VIDEOPROFILE_H264HIGH444PREDICTIVEPROFILE: |
- return "h264high444predictive"; |
- case PP_VIDEOPROFILE_H264SCALABLEBASELINE: |
- return "h264scalablebaseline"; |
- case PP_VIDEOPROFILE_H264SCALABLEHIGH: |
- return "h264scalablehigh"; |
- case PP_VIDEOPROFILE_H264STEREOHIGH: |
- return "h264stereohigh"; |
- case PP_VIDEOPROFILE_H264MULTIVIEWHIGH: |
- return "h264multiviewhigh"; |
- case PP_VIDEOPROFILE_VP8_ANY: |
- return "vp8"; |
- case PP_VIDEOPROFILE_VP9_ANY: |
- return "vp9"; |
- // No default to catch unhandled profiles. |
+// IVF container writer. It is possible to parse H264 bitstream using |
+// NAL units but for VP8 we need a container to at least find encoded |
+// pictures as well as the picture sizes. |
+class IVFWriter { |
+ public: |
+ IVFWriter() {} |
+ ~IVFWriter() {} |
+ |
+ uint32_t GetFileHeaderSize() const { return 32; } |
+ uint32_t GetFrameHeaderSize() const { return 12; } |
+ uint32_t WriteFileHeader(uint8_t* mem, int32_t width, int32_t height); |
+ uint32_t WriteFrameHeader(uint8_t* mem, uint64_t pts, size_t frame_size); |
+ |
+ private: |
+ void PutLE16(uint8_t* mem, int val) const { |
+ mem[0] = (val >> 0) & 0xff; |
+ mem[1] = (val >> 8) & 0xff; |
} |
- return "unknown"; |
-} |
- |
-std::string HardwareAccelerationToString(PP_HardwareAcceleration acceleration) { |
- switch (acceleration) { |
- case PP_HARDWAREACCELERATION_ONLY: |
- return "hardware"; |
- case PP_HARDWAREACCELERATION_WITHFALLBACK: |
- return "hardware/software"; |
- case PP_HARDWAREACCELERATION_NONE: |
- return "software"; |
- // No default to catch unhandled accelerations. |
+ void PutLE32(uint8_t* mem, int val) const { |
+ mem[0] = (val >> 0) & 0xff; |
+ mem[1] = (val >> 8) & 0xff; |
+ mem[2] = (val >> 16) & 0xff; |
+ mem[3] = (val >> 24) & 0xff; |
} |
- return "unknown"; |
+}; |
+ |
+uint32_t IVFWriter::WriteFileHeader(uint8_t* mem, |
+ int32_t width, |
+ int32_t height) { |
+ mem[0] = 'D'; |
+ mem[1] = 'K'; |
+ mem[2] = 'I'; |
+ mem[3] = 'F'; |
+ PutLE16(mem + 4, 0); // version |
+ PutLE16(mem + 6, 32); // header size |
+ PutLE32(mem + 8, fourcc('V', 'P', '8', '0')); // fourcc |
+ PutLE16(mem + 12, static_cast<uint16_t>(width)); // width |
+ PutLE16(mem + 14, static_cast<uint16_t>(height)); // height |
+ PutLE32(mem + 16, 30); // rate |
+ PutLE32(mem + 20, 1); // scale |
+ PutLE32(mem + 24, 0xffffffff); // length |
+ PutLE32(mem + 28, 0); // unused |
+ |
+ return 32; |
+} |
+ |
+uint32_t IVFWriter::WriteFrameHeader(uint8_t* mem, |
+ uint64_t pts, |
+ size_t frame_size) { |
+ PutLE32(mem, (int)frame_size); |
+ PutLE32(mem + 4, (int)(pts & 0xFFFFFFFF)); |
+ PutLE32(mem + 8, (int)(pts >> 32)); |
+ |
+ return 12; |
} |
// This object is the global object representing this plugin library as long |
@@ -106,11 +118,17 @@ class VideoEncoderInstance : public pp::Instance { |
virtual void HandleMessage(const pp::Var& var_message); |
private: |
+ void AddVideoProfile(PP_VideoProfile profile, const std::string& profile_str); |
+ void InitializeVideoProfiles(); |
+ PP_VideoProfile VideoProfileFromString(const std::string& str); |
+ std::string VideoProfileToString(PP_VideoProfile profile); |
+ |
void ConfigureTrack(); |
void OnConfiguredTrack(int32_t result); |
void ProbeEncoder(); |
void OnEncoderProbed(int32_t result, |
const std::vector<PP_VideoProfileDescription> profiles); |
+ void StartEncoder(); |
void OnInitializedEncoder(int32_t result); |
void ScheduleNextEncode(); |
void GetEncoderFrameTick(int32_t result); |
@@ -134,6 +152,12 @@ class VideoEncoderInstance : public pp::Instance { |
void PostDataMessage(const void* buffer, uint32_t size); |
void PostSignalMessage(const char* name); |
+ typedef std::map<std::string, PP_VideoProfile> VideoProfileFromStringMap; |
+ VideoProfileFromStringMap profile_from_string_; |
+ |
+ typedef std::map<PP_VideoProfile, std::string> VideoProfileToStringMap; |
+ VideoProfileToStringMap profile_to_string_; |
+ |
bool is_encoding_; |
bool is_receiving_track_frames_; |
@@ -150,6 +174,8 @@ class VideoEncoderInstance : public pp::Instance { |
uint32_t encoded_frames_; |
pp::VideoFrame current_track_frame_; |
+ |
+ IVFWriter ivf_writer_; |
}; |
VideoEncoderInstance::VideoEncoderInstance(PP_Instance instance, |
@@ -164,11 +190,52 @@ VideoEncoderInstance::VideoEncoderInstance(PP_Instance instance, |
#endif |
frame_format_(PP_VIDEOFRAME_FORMAT_I420), |
encoded_frames_(0) { |
+ InitializeVideoProfiles(); |
+ ProbeEncoder(); |
} |
VideoEncoderInstance::~VideoEncoderInstance() { |
} |
+void VideoEncoderInstance::AddVideoProfile(PP_VideoProfile profile, |
+ const std::string& profile_str) { |
+ profile_to_string_.insert(std::make_pair(profile, profile_str)); |
+ profile_from_string_.insert(std::make_pair(profile_str, profile)); |
+} |
+ |
+void VideoEncoderInstance::InitializeVideoProfiles() { |
+ AddVideoProfile(PP_VIDEOPROFILE_H264BASELINE, "h264baseline"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264MAIN, "h264main"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264EXTENDED, "h264extended"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264HIGH, "h264high"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264HIGH10PROFILE, "h264high10"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264HIGH422PROFILE, "h264high422"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264HIGH444PREDICTIVEPROFILE, |
+ "h264high444predictive"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264SCALABLEBASELINE, "h264scalablebaseline"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264SCALABLEHIGH, "h264scalablehigh"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264STEREOHIGH, "h264stereohigh"); |
+ AddVideoProfile(PP_VIDEOPROFILE_H264MULTIVIEWHIGH, "h264multiviewhigh"); |
+ AddVideoProfile(PP_VIDEOPROFILE_VP8_ANY, "vp8"); |
+ AddVideoProfile(PP_VIDEOPROFILE_VP9_ANY, "vp9"); |
+} |
+ |
+PP_VideoProfile VideoEncoderInstance::VideoProfileFromString( |
+ const std::string& str) { |
+ VideoProfileFromStringMap::iterator it = profile_from_string_.find(str); |
+ if (it == profile_from_string_.end()) |
+ return PP_VIDEOPROFILE_VP8_ANY; |
+ return it->second; |
+} |
+ |
+std::string VideoEncoderInstance::VideoProfileToString( |
+ PP_VideoProfile profile) { |
+ VideoProfileToStringMap::iterator it = profile_to_string_.find(profile); |
+ if (it == profile_to_string_.end()) |
+ return "unknown"; |
+ return it->second; |
+} |
+ |
void VideoEncoderInstance::ConfigureTrack() { |
if (encoder_size_.IsEmpty()) |
frame_size_ = requested_size_; |
@@ -204,7 +271,7 @@ void VideoEncoderInstance::OnConfiguredTrack(int32_t result) { |
StartTrackFrames(); |
ScheduleNextEncode(); |
} else |
- ProbeEncoder(); |
+ StartEncoder(); |
} |
void VideoEncoderInstance::ProbeEncoder() { |
@@ -216,38 +283,25 @@ void VideoEncoderInstance::ProbeEncoder() { |
void VideoEncoderInstance::OnEncoderProbed( |
int32_t result, |
const std::vector<PP_VideoProfileDescription> profiles) { |
- bool has_required_profile = false; |
+ pp::VarDictionary dict; |
+ dict.Set(pp::Var("name"), pp::Var("supportedProfiles")); |
+ pp::VarArray js_profiles; |
+ dict.Set(pp::Var("profiles"), js_profiles); |
- Log("Available profiles:"); |
- for (const PP_VideoProfileDescription& profile : profiles) { |
- std::ostringstream oss; |
- oss << " profile=" << VideoProfileToString(profile.profile) |
- << " max_resolution=" << profile.max_resolution.width << "x" |
- << profile.max_resolution.height |
- << " max_framerate=" << profile.max_framerate_numerator << "/" |
- << profile.max_framerate_denominator << " acceleration=" |
- << HardwareAccelerationToString(profile.acceleration); |
- Log(oss.str()); |
- |
- has_required_profile |= profile.profile == video_profile_; |
+ if (result != PP_OK) { |
+ LogError(result, "Cannot get supported profiles"); |
+ PostMessage(dict); |
} |
- if (!has_required_profile) { |
- std::ostringstream oss; |
- oss << "Cannot find required video profile: "; |
- oss << VideoProfileToString(video_profile_); |
- LogError(PP_ERROR_FAILED, oss.str()); |
- return; |
- } |
+ int32_t idx = 0; |
+ for (const PP_VideoProfileDescription& profile : profiles) |
+ js_profiles.Set(idx++, pp::Var(VideoProfileToString(profile.profile))); |
+ PostMessage(dict); |
+} |
+void VideoEncoderInstance::StartEncoder() { |
video_encoder_ = pp::VideoEncoder(this); |
- pp::VarDictionary dict; |
- dict.Set(pp::Var("status"), pp::Var("initializing encoder")); |
- dict.Set(pp::Var("width"), pp::Var(encoder_size_.width())); |
- dict.Set(pp::Var("height"), pp::Var(encoder_size_.height())); |
- PostMessage(dict); |
- |
int32_t error = video_encoder_.Initialize( |
frame_format_, frame_size_, video_profile_, 2000000, |
PP_HARDWAREACCELERATION_WITHFALLBACK, |
@@ -266,18 +320,13 @@ void VideoEncoderInstance::OnInitializedEncoder(int32_t result) { |
} |
is_encoding_ = true; |
+ PostSignalMessage("started"); |
if (video_encoder_.GetFrameCodedSize(&encoder_size_) != PP_OK) { |
LogError(result, "Cannot get encoder coded frame size"); |
return; |
} |
- pp::VarDictionary dict; |
- dict.Set(pp::Var("status"), pp::Var("encoder initialized")); |
- dict.Set(pp::Var("width"), pp::Var(encoder_size_.width())); |
- dict.Set(pp::Var("height"), pp::Var(encoder_size_.height())); |
- PostMessage(dict); |
- |
video_encoder_.GetBitstreamBuffer(callback_factory_.NewCallbackWithOutput( |
&VideoEncoderInstance::OnGetBitstreamBuffer)); |
@@ -348,6 +397,7 @@ int32_t VideoEncoderInstance::CopyVideoFrame(pp::VideoFrame dest, |
return PP_ERROR_FAILED; |
} |
+ dest.SetTimestamp(src.GetTimestamp()); |
memcpy(dest.GetDataBuffer(), src.GetDataBuffer(), src.GetDataBufferSize()); |
return PP_OK; |
} |
@@ -446,6 +496,8 @@ void VideoEncoderInstance::HandleMessage(const pp::Var& var_message) { |
pp::Resource resource_track = var_track.AsResource(); |
video_track_ = pp::MediaStreamVideoTrack(resource_track); |
video_encoder_ = pp::VideoEncoder(); |
+ video_profile_ = VideoProfileFromString( |
+ dict_message.Get("profile").AsString()); |
ConfigureTrack(); |
} else if (command == "stop") { |
StopEncode(); |
@@ -460,9 +512,34 @@ void VideoEncoderInstance::PostDataMessage(const void* buffer, uint32_t size) { |
dictionary.Set(pp::Var("name"), pp::Var("data")); |
- pp::VarArrayBuffer array_buffer(size); |
- void* data_ptr = array_buffer.Map(); |
- memcpy(data_ptr, buffer, size); |
+ pp::VarArrayBuffer array_buffer; |
+ uint8_t* data_ptr; |
+ uint32_t data_offset = 0; |
+ if (video_profile_ == PP_VIDEOPROFILE_VP8_ANY || |
+ video_profile_ == PP_VIDEOPROFILE_VP9_ANY) { |
+ uint32_t frame_offset = 0; |
+ if (encoded_frames_ == 1) { |
+ array_buffer = pp::VarArrayBuffer( |
+ size + ivf_writer_.GetFileHeaderSize() + |
+ ivf_writer_.GetFrameHeaderSize()); |
+ data_ptr = static_cast<uint8_t*>(array_buffer.Map()); |
+ frame_offset = ivf_writer_.WriteFileHeader( |
+ data_ptr, frame_size_.width(), frame_size_.height()); |
+ } else { |
+ array_buffer = pp::VarArrayBuffer( |
+ size + ivf_writer_.GetFrameHeaderSize()); |
+ data_ptr = static_cast<uint8_t*>(array_buffer.Map()); |
+ } |
+ data_offset = frame_offset + |
+ ivf_writer_.WriteFrameHeader(data_ptr + frame_offset, |
+ encoded_frames_, |
+ size); |
+ } else { |
+ array_buffer = pp::VarArrayBuffer(size); |
+ data_ptr = static_cast<uint8_t*>(array_buffer.Map()); |
+ } |
+ |
+ memcpy(data_ptr + data_offset, buffer, size); |
array_buffer.Unmap(); |
dictionary.Set(pp::Var("data"), array_buffer); |