1 // Copyright 2014 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.
4 
5 #include <stdint.h>
6 #include <stdlib.h>
7 #include <string.h>
8 
9 #include <algorithm>
10 #include <limits>
11 #include <vector>
12 
13 #include "ppapi/cpp/audio_buffer.h"
14 #include "ppapi/cpp/graphics_2d.h"
15 #include "ppapi/cpp/image_data.h"
16 #include "ppapi/cpp/instance.h"
17 #include "ppapi/cpp/logging.h"
18 #include "ppapi/cpp/media_stream_audio_track.h"
19 #include "ppapi/cpp/module.h"
20 #include "ppapi/cpp/rect.h"
21 #include "ppapi/cpp/size.h"
22 #include "ppapi/cpp/var_dictionary.h"
23 #include "ppapi/utility/completion_callback_factory.h"
24 
25 // When compiling natively on Windows, PostMessage can be #define-d to
26 // something else.
27 #ifdef PostMessage
28 #undef PostMessage
29 #endif
30 
31 // This example demonstrates receiving audio samples from an AndioMediaTrack
32 // and visualizing them.
33 
34 namespace {
35 
36 const uint32_t kColorRed = 0xFFFF0000;
37 const uint32_t kColorGreen = 0xFF00FF00;
38 const uint32_t kColorGrey1 = 0xFF202020;
39 const uint32_t kColorGrey2 = 0xFF404040;
40 const uint32_t kColorGrey3 = 0xFF606060;
41 
42 class MediaStreamAudioInstance : public pp::Instance {
43  public:
MediaStreamAudioInstance(PP_Instance instance)44   explicit MediaStreamAudioInstance(PP_Instance instance)
45       : pp::Instance(instance),
46         callback_factory_(this),
47         first_buffer_(true),
48         sample_count_(0),
49         channel_count_(0),
50         timer_interval_(0),
51         pending_paint_(false),
52         waiting_for_flush_completion_(false) {
53   }
54 
~MediaStreamAudioInstance()55   virtual ~MediaStreamAudioInstance() {
56   }
57 
DidChangeView(const pp::Rect & position,const pp::Rect & clip)58   virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
59     if (position.size() == size_)
60       return;
61 
62     size_ = position.size();
63     device_context_ = pp::Graphics2D(this, size_, false);
64     if (!BindGraphics(device_context_))
65       return;
66 
67     Paint();
68   }
69 
HandleMessage(const pp::Var & var_message)70   virtual void HandleMessage(const pp::Var& var_message) {
71     if (!var_message.is_dictionary())
72       return;
73     pp::VarDictionary var_dictionary_message(var_message);
74     pp::Var var_track = var_dictionary_message.Get("track");
75     if (!var_track.is_resource())
76       return;
77 
78     pp::Resource resource_track = var_track.AsResource();
79     audio_track_ = pp::MediaStreamAudioTrack(resource_track);
80     audio_track_.GetBuffer(callback_factory_.NewCallbackWithOutput(
81           &MediaStreamAudioInstance::OnGetBuffer));
82   }
83 
84  private:
ScheduleNextTimer()85   void ScheduleNextTimer() {
86     PP_DCHECK(timer_interval_ > 0);
87     pp::Module::Get()->core()->CallOnMainThread(
88         timer_interval_,
89         callback_factory_.NewCallback(&MediaStreamAudioInstance::OnTimer),
90         0);
91   }
92 
OnTimer(int32_t)93   void OnTimer(int32_t) {
94     ScheduleNextTimer();
95     Paint();
96   }
97 
DidFlush(int32_t result)98   void DidFlush(int32_t result) {
99     waiting_for_flush_completion_ = false;
100     if (pending_paint_)
101       Paint();
102   }
103 
Paint()104   void Paint() {
105     if (waiting_for_flush_completion_) {
106       pending_paint_ = true;
107       return;
108     }
109 
110     pending_paint_ = false;
111 
112     if (size_.IsEmpty())
113       return;  // Nothing to do.
114 
115     pp::ImageData image = PaintImage(size_);
116     if (!image.is_null()) {
117       device_context_.ReplaceContents(&image);
118       waiting_for_flush_completion_ = true;
119       device_context_.Flush(
120           callback_factory_.NewCallback(&MediaStreamAudioInstance::DidFlush));
121     }
122   }
123 
PaintImage(const pp::Size & size)124   pp::ImageData PaintImage(const pp::Size& size) {
125     pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, false);
126     if (image.is_null())
127       return image;
128 
129     // Clear to dark grey.
130     for (int y = 0; y < size.height(); y++) {
131       for (int x = 0; x < size.width(); x++)
132         *image.GetAddr32(pp::Point(x, y)) = kColorGrey1;
133     }
134 
135     int mid_height = size.height() / 2;
136     int max_amplitude = size.height() * 4 / 10;
137 
138     // Draw some lines.
139     for (int x = 0; x < size.width(); x++) {
140       *image.GetAddr32(pp::Point(x, mid_height)) = kColorGrey3;
141       *image.GetAddr32(pp::Point(x, mid_height + max_amplitude)) = kColorGrey2;
142       *image.GetAddr32(pp::Point(x, mid_height - max_amplitude)) = kColorGrey2;
143     }
144 
145 
146     // Draw our samples.
147     for (int x = 0, i = 0;
148          x < std::min(size.width(), static_cast<int>(sample_count_));
149          x++, i += channel_count_) {
150       for (uint32_t ch = 0; ch < std::min(channel_count_, 2U); ++ch) {
151         int y = samples_[i + ch] * max_amplitude /
152                 (std::numeric_limits<int16_t>::max() + 1) + mid_height;
153         *image.GetAddr32(pp::Point(x, y)) = (ch == 0 ? kColorRed : kColorGreen);
154       }
155     }
156 
157     return image;
158   }
159 
160   // Callback that is invoked when new buffers are received.
OnGetBuffer(int32_t result,pp::AudioBuffer buffer)161   void OnGetBuffer(int32_t result, pp::AudioBuffer buffer) {
162     if (result != PP_OK)
163       return;
164 
165     PP_DCHECK(buffer.GetSampleSize() == PP_AUDIOBUFFER_SAMPLESIZE_16_BITS);
166     const char* data = static_cast<const char*>(buffer.GetDataBuffer());
167     uint32_t channels = buffer.GetNumberOfChannels();
168     uint32_t samples = buffer.GetNumberOfSamples() / channels;
169 
170     if (channel_count_ != channels || sample_count_ != samples) {
171       channel_count_ = channels;
172       sample_count_ = samples;
173 
174       samples_.resize(sample_count_ * channel_count_);
175       timer_interval_ = (sample_count_ * 1000) / buffer.GetSampleRate() + 5;
176       // Start the timer for the first buffer.
177       if (first_buffer_) {
178         first_buffer_ = false;
179         ScheduleNextTimer();
180       }
181     }
182 
183     memcpy(samples_.data(), data,
184         sample_count_ * channel_count_ * sizeof(int16_t));
185 
186     audio_track_.RecycleBuffer(buffer);
187     audio_track_.GetBuffer(callback_factory_.NewCallbackWithOutput(
188         &MediaStreamAudioInstance::OnGetBuffer));
189 
190   }
191 
192   pp::MediaStreamAudioTrack audio_track_;
193   pp::CompletionCallbackFactory<MediaStreamAudioInstance> callback_factory_;
194 
195   bool first_buffer_;
196   uint32_t sample_count_;
197   uint32_t channel_count_;
198   std::vector<int16_t> samples_;
199 
200   int32_t timer_interval_;
201 
202   // Painting stuff.
203   pp::Size size_;
204   pp::Graphics2D device_context_;
205   bool pending_paint_;
206   bool waiting_for_flush_completion_;
207 };
208 
209 class MediaStreamAudioModule : public pp::Module {
210  public:
CreateInstance(PP_Instance instance)211   virtual pp::Instance* CreateInstance(PP_Instance instance) {
212     return new MediaStreamAudioInstance(instance);
213   }
214 };
215 
216 }  // namespace
217 
218 namespace pp {
219 
220 // Factory function for your specialization of the Module object.
CreateModule()221 Module* CreateModule() {
222   return new MediaStreamAudioModule();
223 }
224 
225 }  // namespace pp
226