OLD | NEW |
---|---|
(Empty) | |
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. | |
4 | |
5 #include "chromecast/media/audio/audio_output_stream.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/thread_task_runner_handle.h" | |
9 #include "chromecast/media/audio/audio_manager.h" | |
10 #include "chromecast/media/cma/base/cast_decoder_buffer_impl.h" | |
11 #include "chromecast/media/cma/base/decoder_buffer_adapter.h" | |
12 #include "chromecast/media/cma/pipeline/frame_status_cb_impl.h" | |
13 #include "chromecast/public/media/audio_pipeline_device.h" | |
14 #include "chromecast/public/media/decoder_config.h" | |
15 #include "chromecast/public/media/decrypt_context.h" | |
16 #include "chromecast/public/media/media_clock_device.h" | |
17 #include "chromecast/public/media/media_pipeline_backend.h" | |
18 #include "media/audio/fake_audio_worker.h" | |
19 #include "media/base/decoder_buffer.h" | |
20 | |
21 namespace chromecast { | |
22 namespace media { | |
23 | |
24 namespace { | |
25 bool InitClockDevice(MediaClockDevice* clock_device) { | |
26 DCHECK(clock_device); | |
27 DCHECK_EQ(clock_device->GetState(), MediaClockDevice::kStateUninitialized); | |
28 | |
29 if (!clock_device->SetState(media::MediaClockDevice::kStateIdle)) | |
30 return false; | |
31 | |
32 if (!clock_device->ResetTimeline(0)) | |
33 return false; | |
34 | |
35 if (!clock_device->SetRate(1.0)) | |
36 return false; | |
37 | |
38 return true; | |
39 } | |
halliwell
2015/08/28 15:14:18
It seems unfortunate that we have to do this. The
alokp
2015/09/01 00:23:12
Right. Although removing the requirement of a cloc
| |
40 | |
41 bool InitAudioDevice(const ::media::AudioParameters& audio_params, | |
42 AudioPipelineDevice* audio_device) { | |
43 DCHECK(audio_device); | |
44 DCHECK_EQ(audio_device->GetState(), AudioPipelineDevice::kStateUninitialized); | |
45 | |
46 AudioConfig audio_config; | |
47 audio_config.codec = kCodecPCM; | |
48 audio_config.sample_format = kSampleFormatPlanarS16; | |
49 audio_config.bytes_per_channel = audio_params.bits_per_sample() / 8; | |
50 audio_config.channel_number = audio_params.channels(); | |
51 audio_config.samples_per_second = audio_params.sample_rate(); | |
52 audio_config.extra_data = nullptr; | |
53 audio_config.extra_data_size = 0; | |
54 audio_config.is_encrypted = false; | |
55 if (!audio_device->SetConfig(audio_config)) | |
56 return false; | |
57 | |
58 if (!audio_device->SetState(AudioPipelineDevice::kStateIdle)) | |
59 return false; | |
60 | |
61 return true; | |
62 } | |
63 } // namespace | |
64 | |
65 AudioOutputStream::AudioOutputStream( | |
66 const ::media::AudioParameters& audio_params, | |
67 AudioManager* audio_manager) | |
68 : audio_params_(audio_params), | |
69 audio_manager_(audio_manager), | |
70 volume_(1.0), | |
71 audio_device_busy_(false), | |
72 weak_factory_(this) { | |
73 LOG(INFO) << "New AudioOutputStream with: " | |
74 << audio_params_.AsHumanReadableString(); | |
75 } | |
76 | |
77 AudioOutputStream::~AudioOutputStream() {} | |
78 | |
79 bool AudioOutputStream::Open() { | |
80 LOG(INFO) << __FUNCTION__; | |
halliwell
2015/08/28 15:14:18
do we really need all these info logs?
alokp
2015/09/01 00:23:12
We did have this in one backend implementation and
halliwell
2015/09/01 01:10:32
Frequency seems fine, but it doesn't seem like muc
alokp
2015/09/01 17:34:44
Good point about adding 'this' to the logs. I have
| |
81 | |
82 ::media::AudioParameters::Format format = audio_params_.format(); | |
83 if ((format != ::media::AudioParameters::AUDIO_PCM_LINEAR) && | |
84 (format != ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY)) { | |
85 LOG(WARNING) << "Unsupported audio format: " << format; | |
86 return false; | |
87 } | |
88 | |
89 ::media::ChannelLayout channel_layout = audio_params_.channel_layout(); | |
90 if ((channel_layout != ::media::CHANNEL_LAYOUT_MONO) && | |
91 (channel_layout != ::media::CHANNEL_LAYOUT_STEREO)) { | |
92 LOG(WARNING) << "Unsupported channel layout: " << channel_layout; | |
93 return false; | |
94 } | |
95 DCHECK_GE(audio_params_.channels(), 1); | |
96 DCHECK_LE(audio_params_.channels(), 2); | |
97 | |
98 media_pipeline_backend_ = audio_manager_->CreateMediaPipelineBackend(); | |
99 if (!media_pipeline_backend_) { | |
100 LOG(WARNING) << "Failed to create media pipeline backend."; | |
101 return false; | |
102 } | |
103 | |
104 if (!InitClockDevice(media_pipeline_backend_->GetClock())) { | |
105 LOG(WARNING) << "Failed to initialize clock device."; | |
106 return false; | |
107 } | |
108 | |
109 if (!InitAudioDevice(audio_params_, media_pipeline_backend_->GetAudio())) { | |
110 LOG(WARNING) << "Failed to initialize audio device."; | |
111 return false; | |
112 } | |
113 | |
114 audio_bus_ = ::media::AudioBus::Create(audio_params_); | |
115 audio_worker_.reset(new ::media::FakeAudioWorker( | |
116 base::ThreadTaskRunnerHandle::Get(), audio_params_)); | |
117 return true; | |
118 } | |
119 | |
120 void AudioOutputStream::Close() { | |
121 LOG(INFO) << __FUNCTION__; | |
122 | |
123 if (media_pipeline_backend_) { | |
124 media_pipeline_backend_->GetClock()->SetState(MediaClockDevice::kStateIdle); | |
125 media_pipeline_backend_->GetAudio()->SetState( | |
126 AudioPipelineDevice::kStateIdle); | |
127 } | |
128 | |
129 audio_worker_.reset(); | |
slan
2015/08/31 16:59:56
This class relies on Start(), Stop() , and dtor al
alokp
2015/09/01 00:23:11
AudioManager already has this CHECK. In addition F
| |
130 audio_bus_.reset(); | |
131 media_pipeline_backend_.reset(); | |
132 | |
133 // Signal to the manager that we're closed and can be removed. | |
134 // This should be the last call in the function as it deletes "this". | |
135 audio_manager_->ReleaseOutputStream(this); | |
136 } | |
137 | |
138 void AudioOutputStream::Start(AudioSourceCallback* source_callback) { | |
139 LOG(INFO) << __FUNCTION__; | |
140 | |
141 MediaClockDevice* clock_device = media_pipeline_backend_->GetClock(); | |
142 DCHECK(clock_device); | |
143 if (!clock_device->SetState(MediaClockDevice::kStateRunning)) { | |
144 LOG(WARNING) << "Failed to run clock device."; | |
145 return; | |
146 } | |
147 clock_device->SetRate(1.0f); | |
148 | |
149 AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio(); | |
150 DCHECK(audio_device); | |
151 if (!audio_device->SetState(AudioPipelineDevice::kStateRunning)) { | |
152 LOG(WARNING) << "Failed to run audio device."; | |
153 return; | |
154 } | |
155 audio_worker_->Start(base::Bind(&AudioOutputStream::PushFrame, | |
156 weak_factory_.GetWeakPtr(), source_callback)); | |
157 } | |
158 | |
159 void AudioOutputStream::Stop() { | |
160 LOG(INFO) << __FUNCTION__; | |
161 | |
162 MediaClockDevice* clock_device = media_pipeline_backend_->GetClock(); | |
163 DCHECK(clock_device); | |
164 clock_device->SetState(MediaClockDevice::kStateIdle); | |
165 clock_device->SetRate(0.0f); | |
166 | |
167 AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio(); | |
168 DCHECK(audio_device); | |
169 audio_device->SetState(AudioPipelineDevice::kStatePaused); | |
170 audio_worker_->Stop(); | |
171 audio_device_busy_ = false; | |
172 } | |
173 | |
174 void AudioOutputStream::SetVolume(double volume) { | |
175 AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio(); | |
176 DCHECK(audio_device); | |
177 audio_device->SetStreamVolumeMultiplier(volume); | |
178 volume_ = volume; | |
179 } | |
180 | |
181 void AudioOutputStream::GetVolume(double* volume) { | |
182 *volume = volume_; | |
183 } | |
184 | |
185 void AudioOutputStream::PushFrame(AudioSourceCallback* source_callback) { | |
186 DCHECK(source_callback); | |
187 if (audio_device_busy_) { | |
slan
2015/08/31 16:59:56
In what situation does this occur? audio_worker_ w
alokp
2015/09/01 00:23:12
This happens when the backend buffer is full - opp
| |
188 // Skip pulling data if audio device is still busy. | |
189 LOG(WARNING) << "Skipping frame because audio pipeline device is busy."; | |
190 return; | |
191 } | |
192 | |
193 int frame_count = source_callback->OnMoreData(audio_bus_.get(), 0); | |
194 DCHECK_EQ(frame_count, audio_bus_->frames()); | |
195 int buffer_size = frame_count * audio_params_.GetBytesPerFrame(); | |
196 scoped_refptr<::media::DecoderBuffer> decoder_buffer( | |
197 new ::media::DecoderBuffer(buffer_size)); | |
198 audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8, | |
199 decoder_buffer->writable_data()); | |
200 | |
201 AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio(); | |
202 DCHECK(audio_device); | |
203 MediaComponentDevice::FrameStatus status = audio_device->PushFrame( | |
204 nullptr, // decrypt_context | |
205 new CastDecoderBufferImpl(new DecoderBufferAdapter(decoder_buffer)), | |
206 new media::FrameStatusCBImpl( | |
207 base::Bind(&AudioOutputStream::OnPushFrameStatus, | |
slan
2015/08/31 16:59:56
Is there somewhere in the documentation that makes
alokp
2015/09/01 00:23:11
This guarantee should be provided by AudioPipeline
halliwell
2015/09/01 01:10:32
AudioPipelineDevice is a platform backend, we didn
| |
208 weak_factory_.GetWeakPtr(), source_callback))); | |
209 | |
210 if (status == MediaComponentDevice::kFrameFailed) { | |
211 // Note: We cannot call OnPushFrameError directly because it will lead to | |
212 // a recursive lock, which is not supported by base::Lock. | |
213 // This callback is called with a lock held inside FakeAudioWorker. | |
214 // Calling FakeAudioWorker::Stop will try to acquire the lock again. | |
215 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
216 FROM_HERE, base::Bind(&AudioOutputStream::OnPushFrameError, | |
217 weak_factory_.GetWeakPtr(), source_callback)); | |
218 } else if (status == MediaComponentDevice::kFramePending) { | |
219 audio_device_busy_ = true; | |
220 } | |
221 } | |
222 | |
223 void AudioOutputStream::OnPushFrameStatus( | |
224 AudioSourceCallback* source_callback, | |
225 MediaComponentDevice::FrameStatus status) { | |
226 DCHECK(audio_device_busy_); | |
slan
2015/08/31 16:59:56
Is it possible that Stop() is called, while this c
alokp
2015/09/01 00:23:11
You are right.
Luke: Do we ignore the callback if
halliwell
2015/09/01 01:10:32
Comment on PushFrame says "Pushing a pending frame
| |
227 audio_device_busy_ = false; | |
228 | |
229 DCHECK_NE(status, MediaComponentDevice::kFramePending); | |
230 if (status == MediaComponentDevice::kFrameFailed) | |
231 OnPushFrameError(source_callback); | |
232 } | |
233 | |
234 void AudioOutputStream::OnPushFrameError(AudioSourceCallback* source_callback) { | |
235 LOG(WARNING) << "Failed to push frame to audio pipeline device."; | |
236 // Inform audio source about the error and stop pulling data. | |
237 audio_worker_->Stop(); | |
238 source_callback->OnError(this); | |
239 } | |
240 | |
241 } // namespace media | |
242 } // namespace chromecast | |
OLD | NEW |