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/cast_audio_output_stream.h" |
| 6 |
| 7 #include "base/bind.h" |
| 8 #include "base/thread_task_runner_handle.h" |
| 9 #include "chromecast/media/audio/cast_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 } |
| 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 = kSampleFormatS16; |
| 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 CastAudioOutputStream::CastAudioOutputStream( |
| 66 const ::media::AudioParameters& audio_params, |
| 67 CastAudioManager* 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 VLOG(1) << "CastAudioOutputStream " << this << " created with " |
| 74 << audio_params_.AsHumanReadableString(); |
| 75 } |
| 76 |
| 77 CastAudioOutputStream::~CastAudioOutputStream() {} |
| 78 |
| 79 bool CastAudioOutputStream::Open() { |
| 80 ::media::AudioParameters::Format format = audio_params_.format(); |
| 81 DCHECK((format == ::media::AudioParameters::AUDIO_PCM_LINEAR) || |
| 82 (format == ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY)); |
| 83 |
| 84 ::media::ChannelLayout channel_layout = audio_params_.channel_layout(); |
| 85 if ((channel_layout != ::media::CHANNEL_LAYOUT_MONO) && |
| 86 (channel_layout != ::media::CHANNEL_LAYOUT_STEREO)) { |
| 87 LOG(WARNING) << "Unsupported channel layout: " << channel_layout; |
| 88 return false; |
| 89 } |
| 90 DCHECK_GE(audio_params_.channels(), 1); |
| 91 DCHECK_LE(audio_params_.channels(), 2); |
| 92 |
| 93 media_pipeline_backend_ = audio_manager_->CreateMediaPipelineBackend(); |
| 94 if (!media_pipeline_backend_) { |
| 95 LOG(WARNING) << "Failed to create media pipeline backend."; |
| 96 return false; |
| 97 } |
| 98 |
| 99 if (!InitClockDevice(media_pipeline_backend_->GetClock())) { |
| 100 LOG(WARNING) << "Failed to initialize clock device."; |
| 101 return false; |
| 102 } |
| 103 |
| 104 if (!InitAudioDevice(audio_params_, media_pipeline_backend_->GetAudio())) { |
| 105 LOG(WARNING) << "Failed to initialize audio device."; |
| 106 return false; |
| 107 } |
| 108 |
| 109 audio_bus_ = ::media::AudioBus::Create(audio_params_); |
| 110 audio_worker_.reset(new ::media::FakeAudioWorker( |
| 111 base::ThreadTaskRunnerHandle::Get(), audio_params_)); |
| 112 |
| 113 VLOG(1) << __FUNCTION__ << " : " << this; |
| 114 return true; |
| 115 } |
| 116 |
| 117 void CastAudioOutputStream::Close() { |
| 118 VLOG(1) << __FUNCTION__ << " : " << this; |
| 119 |
| 120 if (media_pipeline_backend_) { |
| 121 media_pipeline_backend_->GetClock()->SetState(MediaClockDevice::kStateIdle); |
| 122 media_pipeline_backend_->GetAudio()->SetState( |
| 123 AudioPipelineDevice::kStateIdle); |
| 124 } |
| 125 |
| 126 audio_worker_.reset(); |
| 127 audio_bus_.reset(); |
| 128 media_pipeline_backend_.reset(); |
| 129 |
| 130 // Signal to the manager that we're closed and can be removed. |
| 131 // This should be the last call in the function as it deletes "this". |
| 132 audio_manager_->ReleaseOutputStream(this); |
| 133 } |
| 134 |
| 135 void CastAudioOutputStream::Start(AudioSourceCallback* source_callback) { |
| 136 MediaClockDevice* clock_device = media_pipeline_backend_->GetClock(); |
| 137 DCHECK(clock_device); |
| 138 if (!clock_device->SetState(MediaClockDevice::kStateRunning)) { |
| 139 LOG(WARNING) << "Failed to run clock device."; |
| 140 return; |
| 141 } |
| 142 clock_device->SetRate(1.0f); |
| 143 |
| 144 AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio(); |
| 145 DCHECK(audio_device); |
| 146 if (!audio_device->SetState(AudioPipelineDevice::kStateRunning)) { |
| 147 LOG(WARNING) << "Failed to run audio device."; |
| 148 return; |
| 149 } |
| 150 |
| 151 VLOG(1) << __FUNCTION__ << " : " << this; |
| 152 audio_worker_->Start(base::Bind(&CastAudioOutputStream::PushFrame, |
| 153 weak_factory_.GetWeakPtr(), source_callback)); |
| 154 } |
| 155 |
| 156 void CastAudioOutputStream::Stop() { |
| 157 VLOG(1) << __FUNCTION__ << " : " << this; |
| 158 |
| 159 MediaClockDevice* clock_device = media_pipeline_backend_->GetClock(); |
| 160 DCHECK(clock_device); |
| 161 clock_device->SetState(MediaClockDevice::kStateIdle); |
| 162 clock_device->SetRate(0.0f); |
| 163 |
| 164 AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio(); |
| 165 DCHECK(audio_device); |
| 166 audio_device->SetState(AudioPipelineDevice::kStatePaused); |
| 167 audio_worker_->Stop(); |
| 168 audio_device_busy_ = false; |
| 169 } |
| 170 |
| 171 void CastAudioOutputStream::SetVolume(double volume) { |
| 172 AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio(); |
| 173 DCHECK(audio_device); |
| 174 audio_device->SetStreamVolumeMultiplier(volume); |
| 175 volume_ = volume; |
| 176 } |
| 177 |
| 178 void CastAudioOutputStream::GetVolume(double* volume) { |
| 179 *volume = volume_; |
| 180 } |
| 181 |
| 182 void CastAudioOutputStream::PushFrame(AudioSourceCallback* source_callback) { |
| 183 DCHECK(source_callback); |
| 184 if (audio_device_busy_) { |
| 185 // Skip pulling data if audio device is still busy. |
| 186 LOG(WARNING) << __FUNCTION__ << " : " << this |
| 187 << " skipped because audio device is busy."; |
| 188 return; |
| 189 } |
| 190 |
| 191 int frame_count = source_callback->OnMoreData(audio_bus_.get(), 0); |
| 192 DCHECK_EQ(frame_count, audio_bus_->frames()); |
| 193 int buffer_size = frame_count * audio_params_.GetBytesPerFrame(); |
| 194 scoped_refptr<::media::DecoderBuffer> decoder_buffer( |
| 195 new ::media::DecoderBuffer(buffer_size)); |
| 196 audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8, |
| 197 decoder_buffer->writable_data()); |
| 198 |
| 199 AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio(); |
| 200 DCHECK(audio_device); |
| 201 MediaComponentDevice::FrameStatus status = audio_device->PushFrame( |
| 202 nullptr, // decrypt_context |
| 203 new CastDecoderBufferImpl(new DecoderBufferAdapter(decoder_buffer)), |
| 204 new media::FrameStatusCBImpl( |
| 205 base::Bind(&CastAudioOutputStream::OnPushFrameStatus, |
| 206 weak_factory_.GetWeakPtr(), source_callback))); |
| 207 |
| 208 if (status == MediaComponentDevice::kFrameFailed) { |
| 209 // Note: We cannot call OnPushFrameError directly because it will lead to |
| 210 // a recursive lock, which is not supported by base::Lock. |
| 211 // This callback is called with a lock held inside FakeAudioWorker. |
| 212 // Calling FakeAudioWorker::Stop will try to acquire the lock again. |
| 213 base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 214 FROM_HERE, base::Bind(&CastAudioOutputStream::OnPushFrameError, |
| 215 weak_factory_.GetWeakPtr(), source_callback)); |
| 216 } else if (status == MediaComponentDevice::kFramePending) { |
| 217 audio_device_busy_ = true; |
| 218 } |
| 219 } |
| 220 |
| 221 void CastAudioOutputStream::OnPushFrameStatus( |
| 222 AudioSourceCallback* source_callback, |
| 223 MediaComponentDevice::FrameStatus status) { |
| 224 DCHECK(audio_device_busy_); |
| 225 audio_device_busy_ = false; |
| 226 |
| 227 DCHECK_NE(status, MediaComponentDevice::kFramePending); |
| 228 if (status == MediaComponentDevice::kFrameFailed) |
| 229 OnPushFrameError(source_callback); |
| 230 } |
| 231 |
| 232 void CastAudioOutputStream::OnPushFrameError( |
| 233 AudioSourceCallback* source_callback) { |
| 234 LOG(WARNING) << __FUNCTION__ << " : " << this; |
| 235 // Inform audio source about the error and stop pulling data. |
| 236 audio_worker_->Stop(); |
| 237 source_callback->OnError(this); |
| 238 } |
| 239 |
| 240 } // namespace media |
| 241 } // namespace chromecast |
OLD | NEW |