ppapi: VideoEncoder: prevent scheduling encoding multiple times
[chromium-blink-merge.git] / ppapi / examples / video_encode / video_encode.cc
blob229c30ddd42f147ab2b6d450ea392bab9fc4912b
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include <math.h>
6 #include <stdio.h>
7 #include <string.h>
9 #include <algorithm>
10 #include <iostream>
11 #include <map>
12 #include <sstream>
13 #include <vector>
15 #include "ppapi/c/pp_errors.h"
16 #include "ppapi/c/ppb_console.h"
17 #include "ppapi/cpp/input_event.h"
18 #include "ppapi/cpp/instance.h"
19 #include "ppapi/cpp/media_stream_video_track.h"
20 #include "ppapi/cpp/module.h"
21 #include "ppapi/cpp/rect.h"
22 #include "ppapi/cpp/var.h"
23 #include "ppapi/cpp/var_array_buffer.h"
24 #include "ppapi/cpp/var_dictionary.h"
25 #include "ppapi/cpp/video_encoder.h"
26 #include "ppapi/cpp/video_frame.h"
27 #include "ppapi/utility/completion_callback_factory.h"
29 // When compiling natively on Windows, PostMessage can be #define-d to
30 // something else.
31 #ifdef PostMessage
32 #undef PostMessage
33 #endif
35 // Use assert as a poor-man's CHECK, even in non-debug mode.
36 // Since <assert.h> redefines assert on every inclusion (it doesn't use
37 // include-guards), make sure this is the last file #include'd in this file.
38 #undef NDEBUG
39 #include <assert.h>
41 #define fourcc(a, b, c, d) \
42 (((uint32_t)(a) << 0) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | \
43 ((uint32_t)(d) << 24))
45 namespace {
47 double clamp(double min, double max, double value) {
48 return std::max(std::min(value, max), min);
51 // IVF container writer. It is possible to parse H264 bitstream using
52 // NAL units but for VP8 we need a container to at least find encoded
53 // pictures as well as the picture sizes.
54 class IVFWriter {
55 public:
56 IVFWriter() {}
57 ~IVFWriter() {}
59 uint32_t GetFileHeaderSize() const { return 32; }
60 uint32_t GetFrameHeaderSize() const { return 12; }
61 uint32_t WriteFileHeader(uint8_t* mem, int32_t width, int32_t height);
62 uint32_t WriteFrameHeader(uint8_t* mem, uint64_t pts, size_t frame_size);
64 private:
65 void PutLE16(uint8_t* mem, int val) const {
66 mem[0] = (val >> 0) & 0xff;
67 mem[1] = (val >> 8) & 0xff;
69 void PutLE32(uint8_t* mem, int val) const {
70 mem[0] = (val >> 0) & 0xff;
71 mem[1] = (val >> 8) & 0xff;
72 mem[2] = (val >> 16) & 0xff;
73 mem[3] = (val >> 24) & 0xff;
77 uint32_t IVFWriter::WriteFileHeader(uint8_t* mem,
78 int32_t width,
79 int32_t height) {
80 mem[0] = 'D';
81 mem[1] = 'K';
82 mem[2] = 'I';
83 mem[3] = 'F';
84 PutLE16(mem + 4, 0); // version
85 PutLE16(mem + 6, 32); // header size
86 PutLE32(mem + 8, fourcc('V', 'P', '8', '0')); // fourcc
87 PutLE16(mem + 12, static_cast<uint16_t>(width)); // width
88 PutLE16(mem + 14, static_cast<uint16_t>(height)); // height
89 PutLE32(mem + 16, 30); // rate
90 PutLE32(mem + 20, 1); // scale
91 PutLE32(mem + 24, 0xffffffff); // length
92 PutLE32(mem + 28, 0); // unused
94 return 32;
97 uint32_t IVFWriter::WriteFrameHeader(uint8_t* mem,
98 uint64_t pts,
99 size_t frame_size) {
100 PutLE32(mem, (int)frame_size);
101 PutLE32(mem + 4, (int)(pts & 0xFFFFFFFF));
102 PutLE32(mem + 8, (int)(pts >> 32));
104 return 12;
107 // This object is the global object representing this plugin library as long
108 // as it is loaded.
109 class VideoEncoderModule : public pp::Module {
110 public:
111 VideoEncoderModule() : pp::Module() {}
112 virtual ~VideoEncoderModule() {}
114 virtual pp::Instance* CreateInstance(PP_Instance instance);
117 class VideoEncoderInstance : public pp::Instance {
118 public:
119 VideoEncoderInstance(PP_Instance instance, pp::Module* module);
120 virtual ~VideoEncoderInstance();
122 // pp::Instance implementation.
123 virtual void HandleMessage(const pp::Var& var_message);
125 private:
126 void AddVideoProfile(PP_VideoProfile profile, const std::string& profile_str);
127 void InitializeVideoProfiles();
128 PP_VideoProfile VideoProfileFromString(const std::string& str);
129 std::string VideoProfileToString(PP_VideoProfile profile);
131 void ConfigureTrack();
132 void OnConfiguredTrack(int32_t result);
133 void ProbeEncoder();
134 void OnEncoderProbed(int32_t result,
135 const std::vector<PP_VideoProfileDescription> profiles);
136 void StartEncoder();
137 void OnInitializedEncoder(int32_t result);
138 void ScheduleNextEncode();
139 void GetEncoderFrameTick(int32_t result);
140 void GetEncoderFrame(const pp::VideoFrame& track_frame);
141 void OnEncoderFrame(int32_t result,
142 pp::VideoFrame encoder_frame,
143 pp::VideoFrame track_frame);
144 int32_t CopyVideoFrame(pp::VideoFrame dest, pp::VideoFrame src);
145 void EncodeFrame(const pp::VideoFrame& frame);
146 void OnEncodeDone(int32_t result);
147 void OnGetBitstreamBuffer(int32_t result, PP_BitstreamBuffer buffer);
148 void StartTrackFrames();
149 void StopTrackFrames();
150 void OnTrackFrame(int32_t result, pp::VideoFrame frame);
152 void StopEncode();
154 void LogError(int32_t error, const std::string& message);
155 void Log(const std::string& message);
157 void PostDataMessage(const void* buffer, uint32_t size);
158 void PostSignalMessage(const char* name);
160 typedef std::map<std::string, PP_VideoProfile> VideoProfileFromStringMap;
161 VideoProfileFromStringMap profile_from_string_;
163 typedef std::map<PP_VideoProfile, std::string> VideoProfileToStringMap;
164 VideoProfileToStringMap profile_to_string_;
166 bool is_encoding_;
167 bool is_encode_ticking_;
168 bool is_receiving_track_frames_;
170 pp::VideoEncoder video_encoder_;
171 pp::MediaStreamVideoTrack video_track_;
172 pp::CompletionCallbackFactory<VideoEncoderInstance> callback_factory_;
174 PP_VideoProfile video_profile_;
175 PP_VideoFrame_Format frame_format_;
177 pp::Size requested_size_;
178 pp::Size frame_size_;
179 pp::Size encoder_size_;
180 uint32_t encoded_frames_;
182 pp::VideoFrame current_track_frame_;
184 IVFWriter ivf_writer_;
186 PP_Time last_encode_tick_;
189 VideoEncoderInstance::VideoEncoderInstance(PP_Instance instance,
190 pp::Module* module)
191 : pp::Instance(instance),
192 is_encoding_(false),
193 is_encode_ticking_(false),
194 callback_factory_(this),
195 #if defined(USE_VP8_INSTEAD_OF_H264)
196 video_profile_(PP_VIDEOPROFILE_VP8_ANY),
197 #else
198 video_profile_(PP_VIDEOPROFILE_H264MAIN),
199 #endif
200 frame_format_(PP_VIDEOFRAME_FORMAT_I420),
201 encoded_frames_(0),
202 last_encode_tick_(0) {
203 InitializeVideoProfiles();
204 ProbeEncoder();
207 VideoEncoderInstance::~VideoEncoderInstance() {
210 void VideoEncoderInstance::AddVideoProfile(PP_VideoProfile profile,
211 const std::string& profile_str) {
212 profile_to_string_.insert(std::make_pair(profile, profile_str));
213 profile_from_string_.insert(std::make_pair(profile_str, profile));
216 void VideoEncoderInstance::InitializeVideoProfiles() {
217 AddVideoProfile(PP_VIDEOPROFILE_H264BASELINE, "h264baseline");
218 AddVideoProfile(PP_VIDEOPROFILE_H264MAIN, "h264main");
219 AddVideoProfile(PP_VIDEOPROFILE_H264EXTENDED, "h264extended");
220 AddVideoProfile(PP_VIDEOPROFILE_H264HIGH, "h264high");
221 AddVideoProfile(PP_VIDEOPROFILE_H264HIGH10PROFILE, "h264high10");
222 AddVideoProfile(PP_VIDEOPROFILE_H264HIGH422PROFILE, "h264high422");
223 AddVideoProfile(PP_VIDEOPROFILE_H264HIGH444PREDICTIVEPROFILE,
224 "h264high444predictive");
225 AddVideoProfile(PP_VIDEOPROFILE_H264SCALABLEBASELINE, "h264scalablebaseline");
226 AddVideoProfile(PP_VIDEOPROFILE_H264SCALABLEHIGH, "h264scalablehigh");
227 AddVideoProfile(PP_VIDEOPROFILE_H264STEREOHIGH, "h264stereohigh");
228 AddVideoProfile(PP_VIDEOPROFILE_H264MULTIVIEWHIGH, "h264multiviewhigh");
229 AddVideoProfile(PP_VIDEOPROFILE_VP8_ANY, "vp8");
230 AddVideoProfile(PP_VIDEOPROFILE_VP9_ANY, "vp9");
233 PP_VideoProfile VideoEncoderInstance::VideoProfileFromString(
234 const std::string& str) {
235 VideoProfileFromStringMap::iterator it = profile_from_string_.find(str);
236 if (it == profile_from_string_.end())
237 return PP_VIDEOPROFILE_VP8_ANY;
238 return it->second;
241 std::string VideoEncoderInstance::VideoProfileToString(
242 PP_VideoProfile profile) {
243 VideoProfileToStringMap::iterator it = profile_to_string_.find(profile);
244 if (it == profile_to_string_.end())
245 return "unknown";
246 return it->second;
249 void VideoEncoderInstance::ConfigureTrack() {
250 if (encoder_size_.IsEmpty())
251 frame_size_ = requested_size_;
252 else
253 frame_size_ = encoder_size_;
255 int32_t attrib_list[] = {PP_MEDIASTREAMVIDEOTRACK_ATTRIB_FORMAT,
256 frame_format_,
257 PP_MEDIASTREAMVIDEOTRACK_ATTRIB_WIDTH,
258 frame_size_.width(),
259 PP_MEDIASTREAMVIDEOTRACK_ATTRIB_HEIGHT,
260 frame_size_.height(),
261 PP_MEDIASTREAMVIDEOTRACK_ATTRIB_NONE};
263 pp::VarDictionary dict;
264 dict.Set(pp::Var("status"), pp::Var("configuring video track"));
265 dict.Set(pp::Var("width"), pp::Var(frame_size_.width()));
266 dict.Set(pp::Var("height"), pp::Var(frame_size_.height()));
267 PostMessage(dict);
269 video_track_.Configure(
270 attrib_list,
271 callback_factory_.NewCallback(&VideoEncoderInstance::OnConfiguredTrack));
274 void VideoEncoderInstance::OnConfiguredTrack(int32_t result) {
275 if (result != PP_OK) {
276 LogError(result, "Cannot configure track");
277 return;
280 if (is_encoding_) {
281 StartTrackFrames();
282 ScheduleNextEncode();
283 } else
284 StartEncoder();
287 void VideoEncoderInstance::ProbeEncoder() {
288 video_encoder_ = pp::VideoEncoder(this);
289 video_encoder_.GetSupportedProfiles(callback_factory_.NewCallbackWithOutput(
290 &VideoEncoderInstance::OnEncoderProbed));
293 void VideoEncoderInstance::OnEncoderProbed(
294 int32_t result,
295 const std::vector<PP_VideoProfileDescription> profiles) {
296 pp::VarDictionary dict;
297 dict.Set(pp::Var("name"), pp::Var("supportedProfiles"));
298 pp::VarArray js_profiles;
299 dict.Set(pp::Var("profiles"), js_profiles);
301 if (result < 0) {
302 LogError(result, "Cannot get supported profiles");
303 PostMessage(dict);
306 int32_t idx = 0;
307 for (const PP_VideoProfileDescription& profile : profiles)
308 js_profiles.Set(idx++, pp::Var(VideoProfileToString(profile.profile)));
309 PostMessage(dict);
312 void VideoEncoderInstance::StartEncoder() {
313 video_encoder_ = pp::VideoEncoder(this);
315 int32_t error = video_encoder_.Initialize(
316 frame_format_, frame_size_, video_profile_, 2000000,
317 PP_HARDWAREACCELERATION_WITHFALLBACK,
318 callback_factory_.NewCallback(
319 &VideoEncoderInstance::OnInitializedEncoder));
320 if (error != PP_OK_COMPLETIONPENDING) {
321 LogError(error, "Cannot initialize encoder");
322 return;
326 void VideoEncoderInstance::OnInitializedEncoder(int32_t result) {
327 if (result != PP_OK) {
328 LogError(result, "Encoder initialization failed");
329 return;
332 is_encoding_ = true;
333 PostSignalMessage("started");
335 if (video_encoder_.GetFrameCodedSize(&encoder_size_) != PP_OK) {
336 LogError(result, "Cannot get encoder coded frame size");
337 return;
340 video_encoder_.GetBitstreamBuffer(callback_factory_.NewCallbackWithOutput(
341 &VideoEncoderInstance::OnGetBitstreamBuffer));
343 if (encoder_size_ != frame_size_)
344 ConfigureTrack();
345 else {
346 StartTrackFrames();
347 ScheduleNextEncode();
351 void VideoEncoderInstance::ScheduleNextEncode() {
352 // Avoid scheduling more than once at a time.
353 if (is_encode_ticking_)
354 return;
356 PP_Time now = pp::Module::Get()->core()->GetTime();
357 PP_Time tick = 1.0 / 30;
358 // If the callback was triggered late, we need to account for that
359 // delay for the next tick.
360 PP_Time delta = tick - clamp(0, tick, now - last_encode_tick_ - tick);
362 pp::Module::Get()->core()->CallOnMainThread(
363 delta * 1000,
364 callback_factory_.NewCallback(&VideoEncoderInstance::GetEncoderFrameTick),
367 last_encode_tick_ = now;
368 is_encode_ticking_ = true;
371 void VideoEncoderInstance::GetEncoderFrameTick(int32_t result) {
372 is_encode_ticking_ = false;
374 if (is_encoding_) {
375 if (!current_track_frame_.is_null()) {
376 pp::VideoFrame frame = current_track_frame_;
377 current_track_frame_.detach();
378 GetEncoderFrame(frame);
380 ScheduleNextEncode();
384 void VideoEncoderInstance::GetEncoderFrame(const pp::VideoFrame& track_frame) {
385 video_encoder_.GetVideoFrame(callback_factory_.NewCallbackWithOutput(
386 &VideoEncoderInstance::OnEncoderFrame, track_frame));
389 void VideoEncoderInstance::OnEncoderFrame(int32_t result,
390 pp::VideoFrame encoder_frame,
391 pp::VideoFrame track_frame) {
392 if (result == PP_ERROR_ABORTED) {
393 video_track_.RecycleFrame(track_frame);
394 return;
396 if (result != PP_OK) {
397 video_track_.RecycleFrame(track_frame);
398 LogError(result, "Cannot get video frame from video encoder");
399 return;
402 track_frame.GetSize(&frame_size_);
404 if (frame_size_ != encoder_size_) {
405 video_track_.RecycleFrame(track_frame);
406 LogError(PP_ERROR_FAILED, "MediaStreamVideoTrack frame size incorrect");
407 return;
410 if (CopyVideoFrame(encoder_frame, track_frame) == PP_OK)
411 EncodeFrame(encoder_frame);
412 video_track_.RecycleFrame(track_frame);
415 int32_t VideoEncoderInstance::CopyVideoFrame(pp::VideoFrame dest,
416 pp::VideoFrame src) {
417 if (dest.GetDataBufferSize() < src.GetDataBufferSize()) {
418 std::ostringstream oss;
419 oss << "Incorrect destination video frame buffer size : "
420 << dest.GetDataBufferSize() << " < " << src.GetDataBufferSize();
421 LogError(PP_ERROR_FAILED, oss.str());
422 return PP_ERROR_FAILED;
425 dest.SetTimestamp(src.GetTimestamp());
426 memcpy(dest.GetDataBuffer(), src.GetDataBuffer(), src.GetDataBufferSize());
427 return PP_OK;
430 void VideoEncoderInstance::EncodeFrame(const pp::VideoFrame& frame) {
431 video_encoder_.Encode(
432 frame, PP_FALSE,
433 callback_factory_.NewCallback(&VideoEncoderInstance::OnEncodeDone));
436 void VideoEncoderInstance::OnEncodeDone(int32_t result) {
437 if (result == PP_ERROR_ABORTED)
438 return;
439 if (result != PP_OK)
440 LogError(result, "Encode failed");
443 void VideoEncoderInstance::OnGetBitstreamBuffer(int32_t result,
444 PP_BitstreamBuffer buffer) {
445 if (result == PP_ERROR_ABORTED)
446 return;
447 if (result != PP_OK) {
448 LogError(result, "Cannot get bitstream buffer");
449 return;
452 encoded_frames_++;
453 PostDataMessage(buffer.buffer, buffer.size);
454 video_encoder_.RecycleBitstreamBuffer(buffer);
456 video_encoder_.GetBitstreamBuffer(callback_factory_.NewCallbackWithOutput(
457 &VideoEncoderInstance::OnGetBitstreamBuffer));
460 void VideoEncoderInstance::StartTrackFrames() {
461 is_receiving_track_frames_ = true;
462 video_track_.GetFrame(callback_factory_.NewCallbackWithOutput(
463 &VideoEncoderInstance::OnTrackFrame));
466 void VideoEncoderInstance::StopTrackFrames() {
467 is_receiving_track_frames_ = false;
468 if (!current_track_frame_.is_null()) {
469 video_track_.RecycleFrame(current_track_frame_);
470 current_track_frame_.detach();
474 void VideoEncoderInstance::OnTrackFrame(int32_t result, pp::VideoFrame frame) {
475 if (result == PP_ERROR_ABORTED)
476 return;
478 if (!current_track_frame_.is_null()) {
479 video_track_.RecycleFrame(current_track_frame_);
480 current_track_frame_.detach();
483 if (result != PP_OK) {
484 LogError(result, "Cannot get video frame from video track");
485 return;
488 current_track_frame_ = frame;
489 if (is_receiving_track_frames_)
490 video_track_.GetFrame(callback_factory_.NewCallbackWithOutput(
491 &VideoEncoderInstance::OnTrackFrame));
494 void VideoEncoderInstance::StopEncode() {
495 video_encoder_.Close();
496 StopTrackFrames();
497 video_track_.Close();
498 is_encoding_ = false;
499 encoded_frames_ = 0;
504 void VideoEncoderInstance::HandleMessage(const pp::Var& var_message) {
505 if (!var_message.is_dictionary()) {
506 LogToConsole(PP_LOGLEVEL_ERROR, pp::Var("Invalid message!"));
507 return;
510 pp::VarDictionary dict_message(var_message);
511 std::string command = dict_message.Get("command").AsString();
513 if (command == "start") {
514 requested_size_ = pp::Size(dict_message.Get("width").AsInt(),
515 dict_message.Get("height").AsInt());
516 pp::Var var_track = dict_message.Get("track");
517 if (!var_track.is_resource()) {
518 LogToConsole(PP_LOGLEVEL_ERROR, pp::Var("Given track is not a resource"));
519 return;
521 pp::Resource resource_track = var_track.AsResource();
522 video_track_ = pp::MediaStreamVideoTrack(resource_track);
523 video_encoder_ = pp::VideoEncoder();
524 video_profile_ = VideoProfileFromString(
525 dict_message.Get("profile").AsString());
526 ConfigureTrack();
527 } else if (command == "stop") {
528 StopEncode();
529 PostSignalMessage("stopped");
530 } else {
531 LogToConsole(PP_LOGLEVEL_ERROR, pp::Var("Invalid command!"));
535 void VideoEncoderInstance::PostDataMessage(const void* buffer, uint32_t size) {
536 pp::VarDictionary dictionary;
538 dictionary.Set(pp::Var("name"), pp::Var("data"));
540 pp::VarArrayBuffer array_buffer;
541 uint8_t* data_ptr;
542 uint32_t data_offset = 0;
543 if (video_profile_ == PP_VIDEOPROFILE_VP8_ANY ||
544 video_profile_ == PP_VIDEOPROFILE_VP9_ANY) {
545 uint32_t frame_offset = 0;
546 if (encoded_frames_ == 1) {
547 array_buffer = pp::VarArrayBuffer(
548 size + ivf_writer_.GetFileHeaderSize() +
549 ivf_writer_.GetFrameHeaderSize());
550 data_ptr = static_cast<uint8_t*>(array_buffer.Map());
551 frame_offset = ivf_writer_.WriteFileHeader(
552 data_ptr, frame_size_.width(), frame_size_.height());
553 } else {
554 array_buffer = pp::VarArrayBuffer(
555 size + ivf_writer_.GetFrameHeaderSize());
556 data_ptr = static_cast<uint8_t*>(array_buffer.Map());
558 data_offset = frame_offset +
559 ivf_writer_.WriteFrameHeader(data_ptr + frame_offset,
560 encoded_frames_,
561 size);
562 } else {
563 array_buffer = pp::VarArrayBuffer(size);
564 data_ptr = static_cast<uint8_t*>(array_buffer.Map());
567 memcpy(data_ptr + data_offset, buffer, size);
568 array_buffer.Unmap();
569 dictionary.Set(pp::Var("data"), array_buffer);
571 PostMessage(dictionary);
574 void VideoEncoderInstance::PostSignalMessage(const char* name) {
575 pp::VarDictionary dictionary;
576 dictionary.Set(pp::Var("name"), pp::Var(name));
578 PostMessage(dictionary);
581 void VideoEncoderInstance::LogError(int32_t error, const std::string& message) {
582 std::string msg("Error: ");
583 msg.append(pp::Var(error).DebugString());
584 msg.append(" : ");
585 msg.append(message);
586 LogToConsole(PP_LOGLEVEL_ERROR, pp::Var(msg));
589 void VideoEncoderInstance::Log(const std::string& message) {
590 LogToConsole(PP_LOGLEVEL_LOG, pp::Var(message));
593 pp::Instance* VideoEncoderModule::CreateInstance(PP_Instance instance) {
594 return new VideoEncoderInstance(instance, this);
597 } // anonymous namespace
599 namespace pp {
600 // Factory function for your specialization of the Module object.
601 Module* CreateModule() {
602 return new VideoEncoderModule();
604 } // namespace pp