Chromium Code Reviews| Index: examples/audio_play_test/play_tone.cc |
| diff --git a/examples/audio_play_test/play_tone.cc b/examples/audio_play_test/play_tone.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..005cacd51564f4b20fd2acbf0b4d1aa382b002c3 |
| --- /dev/null |
| +++ b/examples/audio_play_test/play_tone.cc |
| @@ -0,0 +1,249 @@ |
| +// 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 <memory> |
| + |
| +#include "base/bind.h" |
| +#include "base/logging.h" |
| +#include "mojo/public/c/system/main.h" |
| +#include "mojo/public/cpp/application/application_delegate.h" |
| +#include "mojo/public/cpp/application/application_impl.h" |
| +#include "mojo/public/cpp/application/application_runner.h" |
| +#include "mojo/public/cpp/utility/run_loop.h" |
| +#include "mojo/services/media/audio/interfaces/audio_server.mojom.h" |
| +#include "mojo/services/media/audio/interfaces/audio_track.mojom.h" |
| +#include "mojo/services/media/common/cpp/circular_buffer_media_pipe_adapter.h" |
| +#include "mojo/services/media/common/cpp/linear_transform.h" |
| +#include "mojo/services/media/common/cpp/local_time.h" |
| +#include "mojo/services/media/common/interfaces/rate_control.mojom.h" |
| + |
| +namespace mojo { |
| +namespace media { |
| +namespace audio { |
| +namespace examples { |
| + |
| +static constexpr uint32_t SAMP_FREQ = 48000; |
| +static constexpr uint32_t CHUNK_USEC = 1000; |
| +static constexpr uint32_t BUF_LO_WATER_USEC = 50000; |
| +static constexpr uint32_t BUF_HI_WATER_USEC = BUF_LO_WATER_USEC |
| + + (4 * CHUNK_USEC); |
| +static constexpr uint32_t BUF_DEPTH_USEC = BUF_HI_WATER_USEC |
| + + (4 * CHUNK_USEC); |
|
jeffbrown
2015/11/04 19:34:43
Is there anything significant about the fact the t
johngro
2015/11/05 00:25:16
not really. There needs to be some distance betwe
|
| +static constexpr uint32_t FRAME_BYTES = 2; |
|
jeffbrown
2015/11/04 19:34:43
sizeof(uint16_t)?
johngro
2015/11/05 00:25:15
sure; but
<pedantic>
sizeof(int16_t) since this is
|
| + |
| +static inline constexpr uint32_t USecToBytes(uint64_t usec) { |
| + return ((usec * SAMP_FREQ) / 1000000) * FRAME_BYTES; |
| +} |
| + |
| +class PlayToneApp : public ApplicationDelegate { |
| + public: |
| + void Initialize(ApplicationImpl* app) override; |
| + |
| + private: |
| + bool GenerateToneCbk(MediaResult res); |
| + void PlayTone(double freq_hz, double amplitude, double duration_sec); |
| + void Cleanup(); |
| + |
| + AudioServerPtr audio_server_; |
| + AudioTrackPtr audio_track_; |
| + RateControlPtr rate_control_; |
| + std::unique_ptr<CircularBufferMediaPipeAdapter> pipe_; |
| + |
| + bool clock_started_ = false; |
| + uint64_t media_time_ = 0; |
| + double freq_hz_ = 440.0; |
| + double amplitude_ = 1.0; |
| +}; |
| + |
| +void PlayToneApp::Initialize(ApplicationImpl* app) { |
| + MediaResult result = MediaResult::UNKNOWN_ERROR; |
| + |
| + app->ConnectToService("mojo:audio_server", &audio_server_); |
| + audio_server_->CreateTrack(GetProxy(&audio_track_)); |
| + |
| + do { |
|
jeffbrown
2015/11/04 19:34:43
This construct with early breaks is a little hard
johngro
2015/11/05 00:25:16
yeah... Classically, I would use gotos for this pa
|
| + // Query the sink's format capabilities. |
| + AudioTrackDescriptorPtr sink_desc; |
| + auto desc_cbk = [&sink_desc](AudioTrackDescriptorPtr desc) { |
| + sink_desc = desc.Pass(); |
| + }; |
| + audio_track_->Describe(AudioTrack::DescribeCallback(desc_cbk)); |
| + |
| + // TODO(johngro): this pattern is awkward. We really don't want to be |
| + // calling WaitForIncomingResponse, even if we were able supply a timeout. |
| + // The best practice would be to defer to a handler for the message we are |
| + // expecting to eventually come back. |
| + // |
| + // But... what if the message never comes back? Perhaps the service is not |
| + // implemented properly, or perhaps the service is malicious. We could |
| + // queue a delayed message on our run loop which indicates a timeout, but |
| + // then what happens when when the response to Describe comes back (as |
| + // expected). We don't really have a good way to cancel the "timeout" |
| + // message once we have queued it. Maintaining all of the bookkeeping |
| + // required to nerf the callback when it happens and is discovered to be |
| + // useless is going to get very old, very fast. |
| + // |
| + // For now, we just do the evil thing and block during init, but I sure do |
| + // wish there was something nicer we could do. |
| + if (!audio_track_.WaitForIncomingResponse()) { |
|
jeffbrown
2015/11/04 19:34:43
The pattern you're looking for here is to do two t
johngro
2015/11/05 00:25:16
Acknowledged.
We should talk sometime about this.
|
| + LOG(ERROR) << "Failed to fetch sync capabilities; no response received."; |
| + break; |
| + } |
| + |
| + // TODO(johngro): do something useful with our capabilities description. |
| + sink_desc.reset(); |
| + |
| + // Grab the rate control interface for our audio renderer. |
| + auto get_rc_cbk = [&result](MediaResult res) { result = res; }; |
| + audio_track_->GetRateControl(GetProxy(&rate_control_), get_rc_cbk); |
| + if (!audio_track_.WaitForIncomingResponse()) { |
|
jeffbrown
2015/11/04 19:34:43
This is our second WaitForIncomingResponse.
I thi
johngro
2015/11/05 00:25:16
Ack.
Re: "why should retrieving this interface ev
|
| + LOG(ERROR) << |
| + "Failed to fetch rate control interface; no response received."; |
| + break; |
| + } |
| + if (result != MediaResult::OK) { |
| + LOG(ERROR) << "Failed to get rate control interface. (res = " |
| + << result << ")"; |
| + break; |
| + } |
| + |
| + // Configure our sink for 16-bit 48KHz mono. |
| + AudioTrackConfigurationPtr cfg = AudioTrackConfiguration::New(); |
| + cfg->max_frames = USecToBytes(BUF_DEPTH_USEC) / FRAME_BYTES; |
| + |
| + LpcmMediaTypeDetailsPtr pcm_cfg = LpcmMediaTypeDetails::New(); |
| + pcm_cfg->sample_format = LpcmSampleFormat::SIGNED_16; |
| + pcm_cfg->samples_per_frame = 1; |
| + pcm_cfg->frames_per_second = SAMP_FREQ; |
| + |
| + cfg->media_type = MediaType::New(); |
| + cfg->media_type->scheme = MediaTypeScheme::LPCM; |
| + cfg->media_type->details = MediaTypeDetails::New(); |
| + cfg->media_type->details->set_lpcm(pcm_cfg.Pass()); |
| + |
| + MediaPipePtr pipe; |
| + { |
| + auto cbk = [&result](MediaResult res) { |
| + result = res; |
| + }; |
| + audio_track_->Configure(cfg.Pass(), GetProxy(&pipe), cbk); |
| + } |
| + |
| + if (!audio_track_.WaitForIncomingResponse()) { |
|
jeffbrown
2015/11/04 19:34:43
We already have the pipe so we can start using it
johngro
2015/11/05 00:25:16
See above. I will switch to this pattern, and re-
|
| + LOG(ERROR) << "Failed to configure sink; no response received."; |
| + break; |
| + } |
| + |
| + if (result != MediaResult::OK) { |
| + LOG(ERROR) << "Failed to configure sink. (res = " |
| + << result << ")"; |
| + break; |
| + } |
| + |
| + |
| + // Now that we are configured and have our media pipe, pass its interface to |
| + // our circular buffer helper, set up our high/low water marks, register our |
| + // callback, and start to buffer our audio. |
| + pipe_.reset(new CircularBufferMediaPipeAdapter(pipe.Pass())); |
| + pipe_->SetSignalCallback( |
|
jeffbrown
2015/11/04 19:34:43
Oddly enough base::Bind is cleaner for stuff like
johngro
2015/11/05 00:25:16
Acknowledged.
base::Bind has its own issues. Wit
|
| + [this](MediaResult res) -> bool { |
|
jeffbrown
2015/11/04 19:34:43
Note the inherent assumption that "this" will outl
johngro
2015/11/05 00:25:16
Indeed. This is one of the other issues I have wi
|
| + return GenerateToneCbk(res); |
| + }); |
| + pipe_->SetWatermarks(USecToBytes(BUF_HI_WATER_USEC), |
| + USecToBytes(BUF_LO_WATER_USEC)); |
| + result = MediaResult::OK; |
| + } while (false); |
| + |
| + if (result != MediaResult::OK) { |
| + Cleanup(); |
| + } |
| +} |
| + |
| +bool PlayToneApp::GenerateToneCbk(MediaResult res) { |
| + using MappedPacket = CircularBufferMediaPipeAdapter::MappedPacket; |
| + MappedPacket mapped_pkt; |
| + |
| + DCHECK_GT(freq_hz_, 0.0); |
| + DCHECK_GE(amplitude_, 0.0); |
| + DCHECK_LE(amplitude_, 1.0); |
| + |
| + if (res != MediaResult::OK) { |
|
jeffbrown
2015/11/04 19:34:43
As we discussed before, it would be more Mojo-like
johngro
2015/11/05 00:25:16
As we discussed before, I will clean all of this u
|
| + LOG(ERROR) << "Fatal error in cbuf (" << res << ")."; |
| + goto error; |
|
jeffbrown
2015/11/04 19:34:43
Not sure what the style guide has to say about got
johngro
2015/11/05 00:25:16
The external style guide actually sidesteps the is
|
| + } |
| + |
| + while (!pipe_->AboveHiWater()) { |
| + res = pipe_->CreateMediaPacket(USecToBytes(CHUNK_USEC), |
| + false, |
| + &mapped_pkt); |
| + if (res != MediaResult::OK) { |
|
jeffbrown
2015/11/04 19:34:43
Why would this ever fail? Is it DCHECK worthy?
johngro
2015/11/05 00:25:16
Please refer to the documentation present for this
|
| + LOG(ERROR) << "Unexpected error when creating media packet (" |
| + << res << ")."; |
| + goto error; |
| + } |
| + |
| + mapped_pkt.packet()->pts = media_time_; |
|
jeffbrown
2015/11/04 19:34:43
Avoid abbreviations. Prefer "presentation_time".
johngro
2015/11/05 00:25:16
I responded to this already, 6 patch sets ago. As
|
| + |
| + for (uint32_t i = 0; i < MappedPacket::kMaxRegions; ++i) { |
| + int16_t* data = reinterpret_cast<int16_t*>(mapped_pkt.data(i)); |
|
jeffbrown
2015/11/04 19:34:43
Would it make sense to embed the data type into Ma
johngro
2015/11/05 00:25:16
no, I don't think so. If it would make you feel b
|
| + uint64_t len = mapped_pkt.length(i); |
| + if (!data) continue; |
|
jeffbrown
2015/11/04 19:34:43
swap the above two lines
johngro
2015/11/05 00:25:16
Done.
|
| + |
| + DCHECK(len && !(len % FRAME_BYTES)); |
| + len /= FRAME_BYTES; |
| + for (uint64_t i = 0; i < len; ++i, ++media_time_) { |
| + double tmp = ((M_PI * 2.0) / SAMP_FREQ) * freq_hz_ * media_time_; |
| + data[i] = std::numeric_limits<int16_t>::max() * amplitude_ * sin(tmp); |
| + } |
| + } |
| + |
| + res = pipe_->SendMediaPacket(&mapped_pkt); |
|
jeffbrown
2015/11/04 19:34:43
Is this blocking behind the scenes?
Kind of annoy
johngro
2015/11/05 00:25:15
no; this is interacting with a client side helper
|
| + if (res != MediaResult::OK) { |
| + LOG(ERROR) << "Unexpected error when sending media packet (" |
| + << res << ")."; |
| + pipe_->CancelMediaPacket(&mapped_pkt); |
| + goto error; |
| + } |
| + } |
| + |
| + if (!clock_started_) { |
| + // In theory, this could be done at compile time using std::ratio, but |
| + // std::ratio is prohibited. |
| + LinearTransform::Ratio audio_rate(SAMP_FREQ, 1); |
| + LinearTransform::Ratio local_time_rate(LocalDuration::period::num, |
| + LocalDuration::period::den); |
| + LinearTransform::Ratio rate; |
| + bool success = LinearTransform::Ratio::Compose(local_time_rate, |
| + audio_rate, |
| + &rate); |
| + DCHECK(success); // assert that there was no loss of precision. |
| + |
| + LOG(INFO) << "Setting rate " << rate; |
| + |
| + rate_control_->SetRate(rate.numerator, rate.denominator); |
| + clock_started_ = true; |
| + } |
| + |
| + return true; |
| + |
| +error: |
| + Cleanup(); |
| + return false; |
| +} |
| + |
| +void PlayToneApp::Cleanup() { |
| + audio_track_.reset(); |
| + audio_server_.reset(); |
| + RunLoop::current()->Quit(); |
| +} |
| + |
| +} // namespace examples |
| +} // namespace audio |
| +} // namespace media |
| +} // namespace mojo |
| + |
| +MojoResult MojoMain(MojoHandle app_request) { |
| + mojo::ApplicationRunner runner(new mojo::media::audio::examples::PlayToneApp); |
| + return runner.Run(app_request); |
| +} |