| // Copyright 2015 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 <math.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <deque> |
| #include <iostream> |
| #include <map> |
| #include <sstream> |
| #include <vector> |
| |
| #include "ppapi/c/pp_errors.h" |
| #include "ppapi/c/ppb_console.h" |
| #include "ppapi/cpp/input_event.h" |
| #include "ppapi/cpp/instance.h" |
| #include "ppapi/cpp/media_stream_video_track.h" |
| #include "ppapi/cpp/module.h" |
| #include "ppapi/cpp/rect.h" |
| #include "ppapi/cpp/var.h" |
| #include "ppapi/cpp/var_array_buffer.h" |
| #include "ppapi/cpp/var_dictionary.h" |
| #include "ppapi/cpp/video_encoder.h" |
| #include "ppapi/cpp/video_frame.h" |
| #include "ppapi/utility/completion_callback_factory.h" |
| |
| // When compiling natively on Windows, PostMessage can be #define-d to |
| // something else. |
| #ifdef PostMessage |
| #undef PostMessage |
| #endif |
| |
| // Use assert as a makeshift CHECK, even in non-debug mode. |
| // Since <assert.h> redefines assert on every inclusion (it doesn't use |
| // include-guards), make sure this is the last file #include'd in this file. |
| #undef NDEBUG |
| #include <assert.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #define fourcc(a, b, c, d) \ |
| (((uint32_t)(a) << 0) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | \ |
| ((uint32_t)(d) << 24)) |
| |
| namespace { |
| |
| double clamp(double min, double max, double value) { |
| return std::max(std::min(value, max), min); |
| } |
| |
| std::string ToUpperString(const std::string& str) { |
| std::string ret; |
| for (uint32_t i = 0; i < str.size(); i++) |
| ret.push_back(static_cast<char>(toupper(str[i]))); |
| return ret; |
| } |
| |
| // 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, |
| const std::string& codec, |
| 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; |
| } |
| 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; |
| } |
| }; |
| |
| uint32_t IVFWriter::WriteFileHeader(uint8_t* mem, |
| const std::string& codec, |
| 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(codec[0], codec[1], codec[2], '0')); // fourcc |
| PutLE16(mem + 12, static_cast<uint16_t>(width)); // width |
| PutLE16(mem + 14, static_cast<uint16_t>(height)); // height |
| PutLE32(mem + 16, 1000); // 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 |
| // as it is loaded. |
| class VideoEncoderModule : public pp::Module { |
| public: |
| VideoEncoderModule() : pp::Module() {} |
| virtual ~VideoEncoderModule() {} |
| |
| virtual pp::Instance* CreateInstance(PP_Instance instance); |
| }; |
| |
| class VideoEncoderInstance : public pp::Instance { |
| public: |
| VideoEncoderInstance(PP_Instance instance, pp::Module* module); |
| virtual ~VideoEncoderInstance(); |
| |
| // pp::Instance implementation. |
| 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); |
| void GetEncoderFrame(const pp::VideoFrame& track_frame); |
| void OnEncoderFrame(int32_t result, |
| pp::VideoFrame encoder_frame, |
| pp::VideoFrame track_frame); |
| int32_t CopyVideoFrame(pp::VideoFrame dest, pp::VideoFrame src); |
| void EncodeFrame(const pp::VideoFrame& frame); |
| void OnEncodeDone(int32_t result); |
| void OnGetBitstreamBuffer(int32_t result, PP_BitstreamBuffer buffer); |
| void StartTrackFrames(); |
| void StopTrackFrames(); |
| void OnTrackFrame(int32_t result, pp::VideoFrame frame); |
| |
| void StopEncode(); |
| |
| void LogError(int32_t error, const std::string& message); |
| void Log(const std::string& message); |
| |
| void PostDataMessage(const void* buffer, uint32_t size); |
| |
| 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_encode_ticking_; |
| bool is_receiving_track_frames_; |
| |
| pp::VideoEncoder video_encoder_; |
| pp::MediaStreamVideoTrack video_track_; |
| pp::CompletionCallbackFactory<VideoEncoderInstance> callback_factory_; |
| |
| PP_VideoProfile video_profile_; |
| PP_VideoFrame_Format frame_format_; |
| |
| pp::Size requested_size_; |
| pp::Size frame_size_; |
| pp::Size encoder_size_; |
| uint32_t encoded_frames_; |
| |
| std::deque<uint64_t> frames_timestamps_; |
| |
| pp::VideoFrame current_track_frame_; |
| |
| IVFWriter ivf_writer_; |
| |
| PP_Time last_encode_tick_; |
| }; |
| |
| VideoEncoderInstance::VideoEncoderInstance(PP_Instance instance, |
| pp::Module* module) |
| : pp::Instance(instance), |
| is_encoding_(false), |
| is_encode_ticking_(false), |
| callback_factory_(this), |
| #if defined(USE_VP8_INSTEAD_OF_H264) |
| video_profile_(PP_VIDEOPROFILE_VP8_ANY), |
| #else |
| video_profile_(PP_VIDEOPROFILE_H264MAIN), |
| #endif |
| frame_format_(PP_VIDEOFRAME_FORMAT_I420), |
| encoded_frames_(0), |
| last_encode_tick_(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_; |
| else |
| frame_size_ = encoder_size_; |
| |
| int32_t attrib_list[] = {PP_MEDIASTREAMVIDEOTRACK_ATTRIB_FORMAT, |
| frame_format_, |
| PP_MEDIASTREAMVIDEOTRACK_ATTRIB_WIDTH, |
| frame_size_.width(), |
| PP_MEDIASTREAMVIDEOTRACK_ATTRIB_HEIGHT, |
| frame_size_.height(), |
| PP_MEDIASTREAMVIDEOTRACK_ATTRIB_NONE}; |
| |
| video_track_.Configure( |
| attrib_list, |
| callback_factory_.NewCallback(&VideoEncoderInstance::OnConfiguredTrack)); |
| } |
| |
| void VideoEncoderInstance::OnConfiguredTrack(int32_t result) { |
| if (result != PP_OK) { |
| LogError(result, "Cannot configure track"); |
| return; |
| } |
| |
| if (is_encoding_) { |
| StartTrackFrames(); |
| ScheduleNextEncode(); |
| } else |
| StartEncoder(); |
| } |
| |
| void VideoEncoderInstance::ProbeEncoder() { |
| video_encoder_ = pp::VideoEncoder(this); |
| video_encoder_.GetSupportedProfiles(callback_factory_.NewCallbackWithOutput( |
| &VideoEncoderInstance::OnEncoderProbed)); |
| } |
| |
| void VideoEncoderInstance::OnEncoderProbed( |
| int32_t result, |
| const std::vector<PP_VideoProfileDescription> profiles) { |
| pp::VarDictionary dict; |
| dict.Set(pp::Var("name"), pp::Var("supportedProfiles")); |
| pp::VarArray js_profiles; |
| dict.Set(pp::Var("profiles"), js_profiles); |
| |
| if (result < 0) { |
| LogError(result, "Cannot get supported profiles"); |
| PostMessage(dict); |
| } |
| |
| int32_t idx = 0; |
| for (uint32_t i = 0; i < profiles.size(); i++) { |
| const PP_VideoProfileDescription& profile = profiles[i]; |
| js_profiles.Set(idx++, pp::Var(VideoProfileToString(profile.profile))); |
| } |
| PostMessage(dict); |
| } |
| |
| void VideoEncoderInstance::StartEncoder() { |
| video_encoder_ = pp::VideoEncoder(this); |
| frames_timestamps_.clear(); |
| |
| int32_t error = video_encoder_.Initialize( |
| frame_format_, frame_size_, video_profile_, 2000000, |
| PP_HARDWAREACCELERATION_WITHFALLBACK, |
| callback_factory_.NewCallback( |
| &VideoEncoderInstance::OnInitializedEncoder)); |
| if (error != PP_OK_COMPLETIONPENDING) { |
| LogError(error, "Cannot initialize encoder"); |
| return; |
| } |
| } |
| |
| void VideoEncoderInstance::OnInitializedEncoder(int32_t result) { |
| if (result != PP_OK) { |
| LogError(result, "Encoder initialization failed"); |
| return; |
| } |
| |
| is_encoding_ = true; |
| Log("started"); |
| |
| if (video_encoder_.GetFrameCodedSize(&encoder_size_) != PP_OK) { |
| LogError(result, "Cannot get encoder coded frame size"); |
| return; |
| } |
| |
| video_encoder_.GetBitstreamBuffer(callback_factory_.NewCallbackWithOutput( |
| &VideoEncoderInstance::OnGetBitstreamBuffer)); |
| |
| if (encoder_size_ != frame_size_) |
| ConfigureTrack(); |
| else { |
| StartTrackFrames(); |
| ScheduleNextEncode(); |
| } |
| } |
| |
| void VideoEncoderInstance::ScheduleNextEncode() { |
| // Avoid scheduling more than once at a time. |
| if (is_encode_ticking_) |
| return; |
| |
| PP_Time now = pp::Module::Get()->core()->GetTime(); |
| PP_Time tick = 1.0 / 30; |
| // If the callback was triggered late, we need to account for that |
| // delay for the next tick. |
| PP_Time delta = tick - clamp(0, tick, now - last_encode_tick_ - tick); |
| |
| pp::Module::Get()->core()->CallOnMainThread( |
| delta * 1000, |
| callback_factory_.NewCallback(&VideoEncoderInstance::GetEncoderFrameTick), |
| 0); |
| |
| last_encode_tick_ = now; |
| is_encode_ticking_ = true; |
| } |
| |
| void VideoEncoderInstance::GetEncoderFrameTick(int32_t result) { |
| is_encode_ticking_ = false; |
| |
| if (is_encoding_) { |
| if (!current_track_frame_.is_null()) { |
| pp::VideoFrame frame = current_track_frame_; |
| current_track_frame_.detach(); |
| GetEncoderFrame(frame); |
| } |
| ScheduleNextEncode(); |
| } |
| } |
| |
| void VideoEncoderInstance::GetEncoderFrame(const pp::VideoFrame& track_frame) { |
| video_encoder_.GetVideoFrame(callback_factory_.NewCallbackWithOutput( |
| &VideoEncoderInstance::OnEncoderFrame, track_frame)); |
| } |
| |
| void VideoEncoderInstance::OnEncoderFrame(int32_t result, |
| pp::VideoFrame encoder_frame, |
| pp::VideoFrame track_frame) { |
| if (result == PP_ERROR_ABORTED) { |
| video_track_.RecycleFrame(track_frame); |
| return; |
| } |
| if (result != PP_OK) { |
| video_track_.RecycleFrame(track_frame); |
| LogError(result, "Cannot get video frame from video encoder"); |
| return; |
| } |
| |
| track_frame.GetSize(&frame_size_); |
| |
| if (frame_size_ != encoder_size_) { |
| video_track_.RecycleFrame(track_frame); |
| LogError(PP_ERROR_FAILED, "MediaStreamVideoTrack frame size incorrect"); |
| return; |
| } |
| |
| if (CopyVideoFrame(encoder_frame, track_frame) == PP_OK) |
| EncodeFrame(encoder_frame); |
| video_track_.RecycleFrame(track_frame); |
| } |
| |
| int32_t VideoEncoderInstance::CopyVideoFrame(pp::VideoFrame dest, |
| pp::VideoFrame src) { |
| if (dest.GetDataBufferSize() < src.GetDataBufferSize()) { |
| std::ostringstream oss; |
| oss << "Incorrect destination video frame buffer size : " |
| << dest.GetDataBufferSize() << " < " << src.GetDataBufferSize(); |
| LogError(PP_ERROR_FAILED, oss.str()); |
| return PP_ERROR_FAILED; |
| } |
| |
| dest.SetTimestamp(src.GetTimestamp()); |
| memcpy(dest.GetDataBuffer(), src.GetDataBuffer(), src.GetDataBufferSize()); |
| return PP_OK; |
| } |
| |
| void VideoEncoderInstance::EncodeFrame(const pp::VideoFrame& frame) { |
| frames_timestamps_.push_back( |
| static_cast<uint64_t>(frame.GetTimestamp() * 1000)); |
| video_encoder_.Encode( |
| frame, PP_FALSE, |
| callback_factory_.NewCallback(&VideoEncoderInstance::OnEncodeDone)); |
| } |
| |
| void VideoEncoderInstance::OnEncodeDone(int32_t result) { |
| if (result == PP_ERROR_ABORTED) |
| return; |
| if (result != PP_OK) |
| LogError(result, "Encode failed"); |
| } |
| |
| void VideoEncoderInstance::OnGetBitstreamBuffer(int32_t result, |
| PP_BitstreamBuffer buffer) { |
| if (result == PP_ERROR_ABORTED) |
| return; |
| if (result != PP_OK) { |
| LogError(result, "Cannot get bitstream buffer"); |
| return; |
| } |
| |
| encoded_frames_++; |
| PostDataMessage(buffer.buffer, buffer.size); |
| video_encoder_.RecycleBitstreamBuffer(buffer); |
| |
| video_encoder_.GetBitstreamBuffer(callback_factory_.NewCallbackWithOutput( |
| &VideoEncoderInstance::OnGetBitstreamBuffer)); |
| } |
| |
| void VideoEncoderInstance::StartTrackFrames() { |
| is_receiving_track_frames_ = true; |
| video_track_.GetFrame(callback_factory_.NewCallbackWithOutput( |
| &VideoEncoderInstance::OnTrackFrame)); |
| } |
| |
| void VideoEncoderInstance::StopTrackFrames() { |
| is_receiving_track_frames_ = false; |
| if (!current_track_frame_.is_null()) { |
| video_track_.RecycleFrame(current_track_frame_); |
| current_track_frame_.detach(); |
| } |
| } |
| |
| void VideoEncoderInstance::OnTrackFrame(int32_t result, pp::VideoFrame frame) { |
| if (result == PP_ERROR_ABORTED) |
| return; |
| |
| if (!current_track_frame_.is_null()) { |
| video_track_.RecycleFrame(current_track_frame_); |
| current_track_frame_.detach(); |
| } |
| |
| if (result != PP_OK) { |
| LogError(result, "Cannot get video frame from video track"); |
| return; |
| } |
| |
| current_track_frame_ = frame; |
| if (is_receiving_track_frames_) |
| video_track_.GetFrame(callback_factory_.NewCallbackWithOutput( |
| &VideoEncoderInstance::OnTrackFrame)); |
| } |
| |
| void VideoEncoderInstance::StopEncode() { |
| video_encoder_.Close(); |
| StopTrackFrames(); |
| video_track_.Close(); |
| is_encoding_ = false; |
| encoded_frames_ = 0; |
| } |
| |
| // |
| |
| void VideoEncoderInstance::HandleMessage(const pp::Var& var_message) { |
| if (!var_message.is_dictionary()) { |
| LogToConsole(PP_LOGLEVEL_ERROR, pp::Var("Invalid message!")); |
| return; |
| } |
| |
| pp::VarDictionary dict_message(var_message); |
| std::string command = dict_message.Get("command").AsString(); |
| |
| if (command == "start") { |
| requested_size_ = pp::Size(dict_message.Get("width").AsInt(), |
| dict_message.Get("height").AsInt()); |
| pp::Var var_track = dict_message.Get("track"); |
| if (!var_track.is_resource()) { |
| LogToConsole(PP_LOGLEVEL_ERROR, pp::Var("Given track is not a resource")); |
| return; |
| } |
| 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(); |
| Log("stopped"); |
| } else { |
| LogToConsole(PP_LOGLEVEL_ERROR, pp::Var("Invalid command!")); |
| } |
| } |
| |
| void VideoEncoderInstance::PostDataMessage(const void* buffer, uint32_t size) { |
| pp::VarDictionary dictionary; |
| |
| dictionary.Set(pp::Var("name"), pp::Var("data")); |
| |
| 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, ToUpperString(VideoProfileToString(video_profile_)), |
| frame_size_.width(), frame_size_.height()); |
| } else { |
| array_buffer = pp::VarArrayBuffer( |
| size + ivf_writer_.GetFrameHeaderSize()); |
| data_ptr = static_cast<uint8_t*>(array_buffer.Map()); |
| } |
| uint64_t timestamp = frames_timestamps_.front(); |
| frames_timestamps_.pop_front(); |
| data_offset = |
| frame_offset + |
| ivf_writer_.WriteFrameHeader(data_ptr + frame_offset, timestamp, 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); |
| |
| PostMessage(dictionary); |
| } |
| |
| void VideoEncoderInstance::LogError(int32_t error, const std::string& message) { |
| std::string msg("Error: "); |
| msg.append(pp::Var(error).DebugString()); |
| msg.append(" : "); |
| msg.append(message); |
| |
| Log(msg); |
| } |
| |
| void VideoEncoderInstance::Log(const std::string& message) { |
| pp::VarDictionary dictionary; |
| dictionary.Set(pp::Var("name"), pp::Var("log")); |
| dictionary.Set(pp::Var("message"), pp::Var(message)); |
| |
| PostMessage(dictionary); |
| } |
| |
| pp::Instance* VideoEncoderModule::CreateInstance(PP_Instance instance) { |
| return new VideoEncoderInstance(instance, this); |
| } |
| |
| } // anonymous namespace |
| |
| namespace pp { |
| // Factory function for your specialization of the Module object. |
| Module* CreateModule() { |
| return new VideoEncoderModule(); |
| } |
| } // namespace pp |