| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <algorithm> | 5 #include <algorithm> |
| 6 #include <limits> | 6 #include <limits> |
| 7 | 7 |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "mojo/services/media/common/cpp/linear_transform.h" | 9 #include "mojo/services/media/common/cpp/linear_transform.h" |
| 10 #include "mojo/services/media/common/cpp/timeline.h" | 10 #include "mojo/services/media/common/cpp/timeline.h" |
| (...skipping 26 matching lines...) Expand all Loading... |
| 37 }, | 37 }, |
| 38 { | 38 { |
| 39 .sample_format = AudioSampleFormat::SIGNED_16, | 39 .sample_format = AudioSampleFormat::SIGNED_16, |
| 40 .min_channels = 1, | 40 .min_channels = 1, |
| 41 .max_channels = 2, | 41 .max_channels = 2, |
| 42 .min_frames_per_second = 1000, | 42 .min_frames_per_second = 1000, |
| 43 .max_frames_per_second = 48000, | 43 .max_frames_per_second = 48000, |
| 44 }, | 44 }, |
| 45 }; | 45 }; |
| 46 | 46 |
| 47 AudioTrackImpl::AudioTrackImpl(InterfaceRequest<AudioTrack> iface, | 47 AudioTrackImpl::AudioTrackImpl(InterfaceRequest<AudioTrack> track_request, |
| 48 InterfaceRequest<MediaRenderer> renderer_request, |
| 48 AudioServerImpl* owner) | 49 AudioServerImpl* owner) |
| 49 : owner_(owner), | 50 : owner_(owner), |
| 50 binding_(this), | 51 track_binding_(this, track_request.Pass()), |
| 52 renderer_binding_(this, renderer_request.Pass()), |
| 51 pipe_(this, owner) { | 53 pipe_(this, owner) { |
| 52 CHECK(nullptr != owner_); | 54 CHECK(nullptr != owner_); |
| 53 binding_.Bind(iface.Pass()); | 55 track_binding_.set_connection_error_handler([this]() -> void { |
| 54 binding_.set_connection_error_handler([this]() -> void { | 56 if (!renderer_binding_.is_bound()) { |
| 55 Shutdown(); | 57 Shutdown(); |
| 58 } |
| 59 }); |
| 60 renderer_binding_.set_connection_error_handler([this]() -> void { |
| 61 if (!track_binding_.is_bound()) { |
| 62 Shutdown(); |
| 63 } |
| 56 }); | 64 }); |
| 57 } | 65 } |
| 58 | 66 |
| 59 AudioTrackImpl::~AudioTrackImpl() { | 67 AudioTrackImpl::~AudioTrackImpl() { |
| 60 // assert that we have been cleanly shutdown already. | 68 // assert that we have been cleanly shutdown already. |
| 61 MOJO_DCHECK(!binding_.is_bound()); | 69 MOJO_DCHECK(!track_binding_.is_bound()); |
| 70 MOJO_DCHECK(!renderer_binding_.is_bound()); |
| 62 } | 71 } |
| 63 | 72 |
| 64 AudioTrackImplPtr AudioTrackImpl::Create(InterfaceRequest<AudioTrack> iface, | 73 AudioTrackImplPtr AudioTrackImpl::Create( |
| 65 AudioServerImpl* owner) { | 74 InterfaceRequest<AudioTrack> track_request, |
| 66 AudioTrackImplPtr ret(new AudioTrackImpl(iface.Pass(), owner)); | 75 InterfaceRequest<MediaRenderer> renderer_request, |
| 76 AudioServerImpl* owner) { |
| 77 AudioTrackImplPtr ret( |
| 78 new AudioTrackImpl(track_request.Pass(), renderer_request.Pass(), owner)); |
| 67 ret->weak_this_ = ret; | 79 ret->weak_this_ = ret; |
| 68 return ret; | 80 return ret; |
| 69 } | 81 } |
| 70 | 82 |
| 71 void AudioTrackImpl::Shutdown() { | 83 void AudioTrackImpl::Shutdown() { |
| 84 if (track_binding_.is_bound()) { |
| 85 track_binding_.set_connection_error_handler(mojo::Closure()); |
| 86 track_binding_.Close(); |
| 87 } |
| 88 |
| 72 // If we are unbound, then we have already been shut down and are just waiting | 89 // If we are unbound, then we have already been shut down and are just waiting |
| 73 // for the service to destroy us. Run some DCHECK sanity checks and get out. | 90 // for the service to destroy us. Run some DCHECK sanity checks and get out. |
| 74 if (!binding_.is_bound()) { | 91 if (!renderer_binding_.is_bound()) { |
| 75 DCHECK(!pipe_.IsInitialized()); | 92 DCHECK(!pipe_.IsInitialized()); |
| 76 DCHECK(!timeline_control_site_.is_bound()); | 93 DCHECK(!timeline_control_site_.is_bound()); |
| 77 DCHECK(!outputs_.size()); | 94 DCHECK(!outputs_.size()); |
| 78 return; | 95 return; |
| 79 } | 96 } |
| 80 | 97 |
| 81 // Close the connection to our client | 98 // Close the connection to our client |
| 82 binding_.set_connection_error_handler(mojo::Closure()); | 99 renderer_binding_.set_connection_error_handler(mojo::Closure()); |
| 83 binding_.Close(); | 100 renderer_binding_.Close(); |
| 84 | 101 |
| 85 // reset all of our internal state and close any other client connections in | 102 // reset all of our internal state and close any other client connections in |
| 86 // the process. | 103 // the process. |
| 87 pipe_.Reset(); | 104 pipe_.Reset(); |
| 88 timeline_control_site_.Reset(); | 105 timeline_control_site_.Reset(); |
| 89 outputs_.clear(); | 106 outputs_.clear(); |
| 90 | 107 |
| 91 DCHECK(owner_); | 108 DCHECK(owner_); |
| 92 AudioTrackImplPtr thiz = weak_this_.lock(); | 109 AudioTrackImplPtr thiz = weak_this_.lock(); |
| 93 owner_->RemoveTrack(thiz); | 110 owner_->RemoveTrack(thiz); |
| 94 } | 111 } |
| 95 | 112 |
| 96 void AudioTrackImpl::Describe(const DescribeCallback& cbk) { | 113 void AudioTrackImpl::GetSupportedMediaTypes( |
| 114 const GetSupportedMediaTypesCallback& cbk) { |
| 97 // Build a minimal descriptor | 115 // Build a minimal descriptor |
| 98 // | 116 // |
| 99 // TODO(johngro): one day, we need to make this description much more rich and | 117 // TODO(johngro): one day, we need to make this description much more rich and |
| 100 // fully describe our capabilities, based on things like what outputs are | 118 // fully describe our capabilities, based on things like what outputs are |
| 101 // available, the class of hardware we are on, and what options we were | 119 // available, the class of hardware we are on, and what options we were |
| 102 // compiled with. | 120 // compiled with. |
| 103 // | 121 // |
| 104 // For now, it would be nice to just be able to have a static const tree of | 122 // For now, it would be nice to just be able to have a static const tree of |
| 105 // capabilities in this translational unit which we could use to construct our | 123 // capabilities in this translational unit which we could use to construct our |
| 106 // message, but the nature of the structures generated by the C++ bindings | 124 // message, but the nature of the structures generated by the C++ bindings |
| 107 // make this difficult. For now, we just create a trivial descriptor entierly | 125 // make this difficult. For now, we just create a trivial descriptor entierly |
| 108 // by hand. | 126 // by hand. |
| 109 AudioTrackDescriptorPtr desc(AudioTrackDescriptor::New()); | 127 Array<MediaTypeSetPtr> supported_media_types = |
| 110 | |
| 111 desc->supported_media_types = | |
| 112 Array<MediaTypeSetPtr>::New(arraysize(kSupportedAudioTypeSets)); | 128 Array<MediaTypeSetPtr>::New(arraysize(kSupportedAudioTypeSets)); |
| 113 | 129 |
| 114 for (size_t i = 0; i < desc->supported_media_types.size(); ++i) { | 130 for (size_t i = 0; i < supported_media_types.size(); ++i) { |
| 115 const MediaTypeSetPtr& mts = | 131 const MediaTypeSetPtr& mts = |
| 116 (desc->supported_media_types[i] = MediaTypeSet::New()); | 132 (supported_media_types[i] = MediaTypeSet::New()); |
| 117 | 133 |
| 118 mts->medium = MediaTypeMedium::AUDIO; | 134 mts->medium = MediaTypeMedium::AUDIO; |
| 119 mts->encodings = Array<String>::New(1); | 135 mts->encodings = Array<String>::New(1); |
| 120 mts->details = MediaTypeSetDetails::New(); | 136 mts->details = MediaTypeSetDetails::New(); |
| 121 | 137 |
| 122 mts->encodings[0] = MediaType::kAudioEncodingLpcm; | 138 mts->encodings[0] = MediaType::kAudioEncodingLpcm; |
| 123 | 139 |
| 124 const auto& s = kSupportedAudioTypeSets[i]; | 140 const auto& s = kSupportedAudioTypeSets[i]; |
| 125 AudioMediaTypeSetDetailsPtr audio_detail = AudioMediaTypeSetDetails::New(); | 141 AudioMediaTypeSetDetailsPtr audio_detail = AudioMediaTypeSetDetails::New(); |
| 126 | 142 |
| 127 audio_detail->sample_format = s.sample_format; | 143 audio_detail->sample_format = s.sample_format; |
| 128 audio_detail->min_channels = s.min_channels; | 144 audio_detail->min_channels = s.min_channels; |
| 129 audio_detail->max_channels = s.max_channels; | 145 audio_detail->max_channels = s.max_channels; |
| 130 audio_detail->min_frames_per_second = s.min_frames_per_second; | 146 audio_detail->min_frames_per_second = s.min_frames_per_second; |
| 131 audio_detail->max_frames_per_second = s.max_frames_per_second; | 147 audio_detail->max_frames_per_second = s.max_frames_per_second; |
| 132 mts->details->set_audio(audio_detail.Pass()); | 148 mts->details->set_audio(audio_detail.Pass()); |
| 133 } | 149 } |
| 134 | 150 |
| 135 cbk.Run(desc.Pass()); | 151 cbk.Run(supported_media_types.Pass()); |
| 136 } | 152 } |
| 137 | 153 |
| 138 void AudioTrackImpl::Configure(AudioTrackConfigurationPtr configuration, | 154 void AudioTrackImpl::SetMediaType(MediaTypePtr media_type) { |
| 139 InterfaceRequest<MediaConsumer> req) { | |
| 140 // Are we already configured? | 155 // Are we already configured? |
| 141 if (pipe_.IsInitialized()) { | 156 if (pipe_.IsInitialized()) { |
| 142 LOG(ERROR) << "Attempting to reconfigure a configured audio track."; | 157 LOG(ERROR) << "Attempting to reconfigure a configured audio track."; |
| 143 Shutdown(); | 158 Shutdown(); |
| 144 return; | 159 return; |
| 145 } | 160 } |
| 146 | 161 |
| 147 // Check the requested configuration. | 162 // Check the requested configuration. |
| 148 if ((configuration->media_type->medium != MediaTypeMedium::AUDIO) || | 163 if ((media_type->medium != MediaTypeMedium::AUDIO) || |
| 149 (configuration->media_type->encoding != MediaType::kAudioEncodingLpcm) || | 164 (media_type->encoding != MediaType::kAudioEncodingLpcm) || |
| 150 (!configuration->media_type->details->is_audio())) { | 165 (!media_type->details->is_audio())) { |
| 151 LOG(ERROR) << "Unsupported configuration requested in " | 166 LOG(ERROR) << "Unsupported configuration requested in " |
| 152 "AudioTrack::Configure. Media type must be LPCM audio."; | 167 "AudioTrack::Configure. Media type must be LPCM audio."; |
| 153 Shutdown(); | 168 Shutdown(); |
| 154 return; | 169 return; |
| 155 } | 170 } |
| 156 | 171 |
| 157 // Search our supported configuration sets to find one compatible with this | 172 // Search our supported configuration sets to find one compatible with this |
| 158 // request. | 173 // request. |
| 159 auto& cfg = configuration->media_type->details->get_audio(); | 174 auto& cfg = media_type->details->get_audio(); |
| 160 size_t i; | 175 size_t i; |
| 161 for (i = 0; i < arraysize(kSupportedAudioTypeSets); ++i) { | 176 for (i = 0; i < arraysize(kSupportedAudioTypeSets); ++i) { |
| 162 const auto& cfg_set = kSupportedAudioTypeSets[i]; | 177 const auto& cfg_set = kSupportedAudioTypeSets[i]; |
| 163 | 178 |
| 164 if ((cfg->sample_format == cfg_set.sample_format) && | 179 if ((cfg->sample_format == cfg_set.sample_format) && |
| 165 (cfg->channels >= cfg_set.min_channels) && | 180 (cfg->channels >= cfg_set.min_channels) && |
| 166 (cfg->channels <= cfg_set.max_channels) && | 181 (cfg->channels <= cfg_set.max_channels) && |
| 167 (cfg->frames_per_second >= cfg_set.min_frames_per_second) && | 182 (cfg->frames_per_second >= cfg_set.min_frames_per_second) && |
| 168 (cfg->frames_per_second <= cfg_set.max_frames_per_second)) { | 183 (cfg->frames_per_second <= cfg_set.max_frames_per_second)) { |
| 169 break; | 184 break; |
| 170 } | 185 } |
| 171 } | 186 } |
| 172 | 187 |
| 173 if (i >= arraysize(kSupportedAudioTypeSets)) { | 188 if (i >= arraysize(kSupportedAudioTypeSets)) { |
| 174 LOG(ERROR) << "Unsupported LPCM configuration requested in " | 189 LOG(ERROR) << "Unsupported LPCM configuration requested in " |
| 175 "AudioTrack::Configure. " | 190 "AudioTrack::Configure. " |
| 176 << "(format = " << cfg->sample_format | 191 << "(format = " << cfg->sample_format |
| 177 << ", channels = " | 192 << ", channels = " |
| 178 << static_cast<uint32_t>(cfg->channels) | 193 << static_cast<uint32_t>(cfg->channels) |
| 179 << ", frames_per_second = " << cfg->frames_per_second | 194 << ", frames_per_second = " << cfg->frames_per_second |
| 180 << ")"; | 195 << ")"; |
| 181 Shutdown(); | 196 Shutdown(); |
| 182 return; | 197 return; |
| 183 } | 198 } |
| 184 | 199 |
| 185 // Sanity check the ratio which relates audio frames to media time. | |
| 186 uint32_t numerator = | |
| 187 static_cast<uint32_t>(configuration->audio_frame_ratio); | |
| 188 uint32_t denominator = | |
| 189 static_cast<uint32_t>(configuration->media_time_ratio); | |
| 190 if ((numerator < 1) || (denominator < 1)) { | |
| 191 LOG(ERROR) << "Invalid (audio frames:media time ticks) ratio (" | |
| 192 << numerator << "/" << denominator << ")"; | |
| 193 Shutdown(); | |
| 194 return; | |
| 195 } | |
| 196 | |
| 197 frames_per_ns_ = | 200 frames_per_ns_ = |
| 198 TimelineRate(cfg->frames_per_second, Timeline::ns_from_seconds(1)); | 201 TimelineRate(cfg->frames_per_second, Timeline::ns_from_seconds(1)); |
| 199 | 202 |
| 200 // Figure out the rate we need to scale by in order to produce our fixed | 203 // Figure out the rate we need to scale by in order to produce our fixed |
| 201 // point timestamps. | 204 // point timestamps. |
| 202 LinearTransform::Ratio frac_scale(1 << PTS_FRACTIONAL_BITS, 1); | 205 LinearTransform::Ratio frac_scale(1 << PTS_FRACTIONAL_BITS, 1); |
| 203 LinearTransform::Ratio frame_scale(LinearTransform::Ratio(numerator, | 206 LinearTransform::Ratio frame_scale(LinearTransform::Ratio(1, 1)); |
| 204 denominator)); | |
| 205 bool no_loss = LinearTransform::Ratio::Compose(frac_scale, | 207 bool no_loss = LinearTransform::Ratio::Compose(frac_scale, |
| 206 frame_scale, | 208 frame_scale, |
| 207 &frame_to_media_ratio_); | 209 &frame_to_media_ratio_); |
| 208 if (!no_loss) { | 210 if (!no_loss) { |
| 209 LOG(ERROR) << "Invalid (audio frames:media time ticks) ratio (" | 211 LOG(ERROR) << "Invalid (audio frames:media time ticks) ratio (1/1)"; |
| 210 << numerator << "/" << denominator << ")"; | |
| 211 Shutdown(); | 212 Shutdown(); |
| 212 return; | 213 return; |
| 213 } | 214 } |
| 214 | 215 |
| 215 // Figure out how many bytes we need to hold the requested number of nSec of | 216 // Figure out how many bytes we need to hold the requested number of nSec of |
| 216 // audio. | 217 // audio. |
| 217 switch (cfg->sample_format) { | 218 switch (cfg->sample_format) { |
| 218 case AudioSampleFormat::UNSIGNED_8: | 219 case AudioSampleFormat::UNSIGNED_8: |
| 219 bytes_per_frame_ = 1; | 220 bytes_per_frame_ = 1; |
| 220 break; | 221 break; |
| 221 | 222 |
| 222 case AudioSampleFormat::SIGNED_16: | 223 case AudioSampleFormat::SIGNED_16: |
| 223 bytes_per_frame_ = 2; | 224 bytes_per_frame_ = 2; |
| 224 break; | 225 break; |
| 225 | 226 |
| 226 case AudioSampleFormat::SIGNED_24_IN_32: | 227 case AudioSampleFormat::SIGNED_24_IN_32: |
| 227 bytes_per_frame_ = 4; | 228 bytes_per_frame_ = 4; |
| 228 break; | 229 break; |
| 229 | 230 |
| 230 default: | 231 default: |
| 231 DCHECK(false); | 232 DCHECK(false); |
| 232 bytes_per_frame_ = 2; | 233 bytes_per_frame_ = 2; |
| 233 break; | 234 break; |
| 234 } | 235 } |
| 235 bytes_per_frame_ *= cfg->channels; | 236 bytes_per_frame_ *= cfg->channels; |
| 236 | 237 |
| 237 // Bind our pipe to the interface request. | |
| 238 if (pipe_.Init(req.Pass()) != MOJO_RESULT_OK) { | |
| 239 LOG(ERROR) << "Failed to media pipe to interface request."; | |
| 240 Shutdown(); | |
| 241 return; | |
| 242 } | |
| 243 | |
| 244 // Stash our configuration. | 238 // Stash our configuration. |
| 245 format_ = cfg.Pass(); | 239 format_ = cfg.Pass(); |
| 246 | 240 |
| 247 // Have the audio output manager initialize our set of outputs. Note; there | 241 // Have the audio output manager initialize our set of outputs. Note; there |
| 248 // is currently no need for a lock here. Methods called from our user-facing | 242 // is currently no need for a lock here. Methods called from our user-facing |
| 249 // interfaces are seriailzed by nature of the mojo framework, and none of the | 243 // interfaces are seriailzed by nature of the mojo framework, and none of the |
| 250 // output manager's threads should ever need to manipulate the set. Cleanup | 244 // output manager's threads should ever need to manipulate the set. Cleanup |
| 251 // of outputs which have gone away is currently handled in a lazy fashion when | 245 // of outputs which have gone away is currently handled in a lazy fashion when |
| 252 // the track fails to promote its weak reference during an operation involving | 246 // the track fails to promote its weak reference during an operation involving |
| 253 // its outputs. | 247 // its outputs. |
| 254 // | 248 // |
| 255 // TODO(johngro): someday, we will need to deal with recalculating properties | 249 // TODO(johngro): someday, we will need to deal with recalculating properties |
| 256 // which depend on a track's current set of outputs (for example, the minimum | 250 // which depend on a track's current set of outputs (for example, the minimum |
| 257 // latency). This will probably be done using a dirty flag in the track | 251 // latency). This will probably be done using a dirty flag in the track |
| 258 // implementations, and scheduling a job to recalculate the properties for the | 252 // implementations, and scheduling a job to recalculate the properties for the |
| 259 // dirty tracks and notify the users as appropriate. | 253 // dirty tracks and notify the users as appropriate. |
| 260 | 254 |
| 261 // If we cannot promote our own weak pointer, something is seriously wrong. | 255 // If we cannot promote our own weak pointer, something is seriously wrong. |
| 262 AudioTrackImplPtr strong_this(weak_this_.lock()); | 256 AudioTrackImplPtr strong_this(weak_this_.lock()); |
| 263 DCHECK(strong_this); | 257 DCHECK(strong_this); |
| 264 DCHECK(owner_); | 258 DCHECK(owner_); |
| 265 owner_->GetOutputManager().SelectOutputsForTrack(strong_this); | 259 owner_->GetOutputManager().SelectOutputsForTrack(strong_this); |
| 266 } | 260 } |
| 267 | 261 |
| 262 void AudioTrackImpl::GetConsumer( |
| 263 InterfaceRequest<MediaConsumer> consumer_request) { |
| 264 // Bind our pipe to the interface request. |
| 265 if (pipe_.Init(consumer_request.Pass()) != MOJO_RESULT_OK) { |
| 266 LOG(ERROR) << "Failed to media pipe to interface request."; |
| 267 Shutdown(); |
| 268 return; |
| 269 } |
| 270 } |
| 271 |
| 268 void AudioTrackImpl::GetTimelineControlSite( | 272 void AudioTrackImpl::GetTimelineControlSite( |
| 269 InterfaceRequest<MediaTimelineControlSite> req) { | 273 InterfaceRequest<MediaTimelineControlSite> req) { |
| 270 timeline_control_site_.Bind(req.Pass()); | 274 timeline_control_site_.Bind(req.Pass()); |
| 271 } | 275 } |
| 272 | 276 |
| 273 void AudioTrackImpl::SetGain(float db_gain) { | 277 void AudioTrackImpl::SetGain(float db_gain) { |
| 274 if (db_gain >= AudioTrack::kMaxGain) { | 278 if (db_gain >= AudioTrack::kMaxGain) { |
| 275 LOG(ERROR) << "Gain value too large (" << db_gain << ") for audio track."; | 279 LOG(ERROR) << "Gain value too large (" << db_gain << ") for audio track."; |
| 276 Shutdown(); | 280 Shutdown(); |
| 277 return; | 281 return; |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 344 DCHECK(output); | 348 DCHECK(output); |
| 345 output->FlushPendingQueue(); | 349 output->FlushPendingQueue(); |
| 346 } | 350 } |
| 347 cbk.Run(); | 351 cbk.Run(); |
| 348 return true; | 352 return true; |
| 349 } | 353 } |
| 350 | 354 |
| 351 } // namespace audio | 355 } // namespace audio |
| 352 } // namespace media | 356 } // namespace media |
| 353 } // namespace mojo | 357 } // namespace mojo |
| OLD | NEW |