Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(282)

Side by Side Diff: media/audio/cras/cras_output.cc

Issue 11959018: Add a unified audio I/O backend for ChromeOS. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Save params, other cleanups suggested by Dale Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « media/audio/cras/cras_output.h ('k') | media/audio/cras/cras_output_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 // The object has one error state: |state_| == kInError. When |state_| ==
6 // kInError, all public API functions will fail with an error (Start() will call
7 // the OnError() function on the callback immediately), or no-op themselves with
8 // the exception of Close(). Even if an error state has been entered, if Open()
9 // has previously returned successfully, Close() must be called.
10
11 #include "media/audio/cras/cras_output.h"
12
13 #include <cras_client.h>
14
15 #include "base/logging.h"
16 #include "media/audio/audio_util.h"
17 #include "media/audio/cras/audio_manager_cras.h"
18 #include "media/audio/linux/alsa_util.h"
19
20 namespace media {
21
22 // Helps make log messages readable.
23 std::ostream& operator<<(std::ostream& os,
24 CrasOutputStream::InternalState state) {
25 switch (state) {
26 case CrasOutputStream::kInError:
27 os << "kInError";
28 break;
29 case CrasOutputStream::kCreated:
30 os << "kCreated";
31 break;
32 case CrasOutputStream::kIsOpened:
33 os << "kIsOpened";
34 break;
35 case CrasOutputStream::kIsPlaying:
36 os << "kIsPlaying";
37 break;
38 case CrasOutputStream::kIsStopped:
39 os << "kIsStopped";
40 break;
41 case CrasOutputStream::kIsClosed:
42 os << "kIsClosed";
43 break;
44 default:
45 os << "UnknownState";
46 break;
47 };
48 return os;
49 }
50
51 // Overview of operation:
52 // 1) An object of CrasOutputStream is created by the AudioManager
53 // factory: audio_man->MakeAudioStream().
54 // 2) Next some thread will call Open(), at that point a client is created and
55 // configured for the correct format and sample rate.
56 // 3) Then Start(source) is called and a stream is added to the CRAS client
57 // which will create its own thread that periodically calls the source for more
58 // data as buffers are being consumed.
59 // 4) When finished Stop() is called, which is handled by stopping the stream.
60 // 5) Finally Close() is called. It cleans up and notifies the audio manager,
61 // which likely will destroy this object.
62
63 CrasOutputStream::CrasOutputStream(const AudioParameters& params,
64 AudioManagerCras* manager)
65 : client_(NULL),
66 stream_id_(0),
67 samples_per_packet_(params.frames_per_buffer()),
68 bytes_per_frame_(0),
69 frame_rate_(params.sample_rate()),
70 num_channels_(params.channels()),
71 pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())),
72 state_(kCreated),
73 volume_(1.0),
74 manager_(manager),
75 source_callback_(NULL),
76 audio_bus_(AudioBus::Create(params)) {
77 // We must have a manager.
78 DCHECK(manager_);
79
80 // Sanity check input values.
81 if (params.sample_rate() <= 0) {
82 LOG(WARNING) << "Unsupported audio frequency.";
83 TransitionTo(kInError);
84 return;
85 }
86
87 if (AudioParameters::AUDIO_PCM_LINEAR != params.format() &&
88 AudioParameters::AUDIO_PCM_LOW_LATENCY != params.format()) {
89 LOG(WARNING) << "Unsupported audio format.";
90 TransitionTo(kInError);
91 return;
92 }
93
94 if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) {
95 LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample();
96 TransitionTo(kInError);
97 return;
98 }
99 }
100
101 CrasOutputStream::~CrasOutputStream() {
102 InternalState current_state = state();
103 DCHECK(current_state == kCreated ||
104 current_state == kIsClosed ||
105 current_state == kInError);
106 }
107
108 bool CrasOutputStream::Open() {
109 if (!CanTransitionTo(kIsOpened)) {
110 NOTREACHED() << "Invalid state: " << state();
111 return false;
112 }
113
114 // We do not need to check if the transition was successful because
115 // CanTransitionTo() was checked above, and it is assumed that this
116 // object's public API is only called on one thread so the state cannot
117 // transition out from under us.
118 TransitionTo(kIsOpened);
119
120 // Create the client and connect to the CRAS server.
121 int err = cras_client_create(&client_);
122 if (err < 0) {
123 LOG(WARNING) << "Couldn't create CRAS client.\n";
124 client_ = NULL;
125 TransitionTo(kInError);
126 return false;
127 }
128 err = cras_client_connect(client_);
129 if (err) {
130 LOG(WARNING) << "Couldn't connect CRAS client.\n";
131 cras_client_destroy(client_);
132 client_ = NULL;
133 TransitionTo(kInError);
134 return false;
135 }
136 // Then start running the client.
137 err = cras_client_run_thread(client_);
138 if (err) {
139 LOG(WARNING) << "Couldn't run CRAS client.\n";
140 cras_client_destroy(client_);
141 client_ = NULL;
142 TransitionTo(kInError);
143 return false;
144 }
145
146 return true;
147 }
148
149 void CrasOutputStream::Close() {
150 // Sanity Check that we can transition to closed.
151 if (TransitionTo(kIsClosed) != kIsClosed) {
152 NOTREACHED() << "Unable to transition Closed.";
153 return;
154 }
155
156 if (client_) {
157 cras_client_stop(client_);
158 cras_client_destroy(client_);
159 client_ = NULL;
160 }
161
162 // Signal to the manager that we're closed and can be removed.
163 // Should be last call in the method as it deletes "this".
164 manager_->ReleaseOutputStream(this);
165 }
166
167 void CrasOutputStream::Start(AudioSourceCallback* callback) {
168 CHECK(callback);
169 source_callback_ = callback;
170
171 // Only start if we can enter the playing state.
172 if (TransitionTo(kIsPlaying) != kIsPlaying)
173 return;
174
175 // Prepare |audio_format| and |stream_params| for the stream we
176 // will create.
177 cras_audio_format* audio_format = cras_audio_format_create(
178 pcm_format_,
179 frame_rate_,
180 num_channels_);
181 if (audio_format == NULL) {
182 LOG(WARNING) << "Error setting up audio parameters.";
183 TransitionTo(kInError);
184 callback->OnError(this);
185 return;
186 }
187 cras_stream_params* stream_params = cras_client_stream_params_create(
188 CRAS_STREAM_OUTPUT,
189 samples_per_packet_ * 2, // Total latency.
190 samples_per_packet_ / 2, // Call back when this many left.
191 samples_per_packet_, // Call back with at least this much space.
192 CRAS_STREAM_TYPE_DEFAULT,
193 0,
194 this,
195 CrasOutputStream::PutSamples,
196 CrasOutputStream::StreamError,
197 audio_format);
198 if (stream_params == NULL) {
199 LOG(WARNING) << "Error setting up stream parameters.";
200 TransitionTo(kInError);
201 callback->OnError(this);
202 cras_audio_format_destroy(audio_format);
203 return;
204 }
205
206 // Before starting the stream, save the number of bytes in a frame for use in
207 // the callback.
208 bytes_per_frame_ = cras_client_format_bytes_per_frame(audio_format);
209
210 // Adding the stream will start the audio callbacks requesting data.
211 int err = cras_client_add_stream(client_, &stream_id_, stream_params);
212 if (err < 0) {
213 LOG(WARNING) << "Failed to add the stream";
214 TransitionTo(kInError);
215 callback->OnError(this);
216 cras_audio_format_destroy(audio_format);
217 cras_client_stream_params_destroy(stream_params);
218 return;
219 }
220
221 // Set initial volume.
222 cras_client_set_stream_volume(client_, stream_id_, volume_);
223
224 // Done with config params.
225 cras_audio_format_destroy(audio_format);
226 cras_client_stream_params_destroy(stream_params);
227 }
228
229 void CrasOutputStream::Stop() {
230 if (!client_)
231 return;
232 // Removing the stream from the client stops audio.
233 cras_client_rm_stream(client_, stream_id_);
234 TransitionTo(kIsStopped);
235 }
236
237 void CrasOutputStream::SetVolume(double volume) {
238 if (!client_)
239 return;
240 volume_ = static_cast<float>(volume);
241 cras_client_set_stream_volume(client_, stream_id_, volume_);
242 }
243
244 void CrasOutputStream::GetVolume(double* volume) {
245 *volume = volume_;
246 }
247
248 // Static callback asking for samples.
249 int CrasOutputStream::PutSamples(cras_client* client,
250 cras_stream_id_t stream_id,
251 uint8* samples,
252 size_t frames,
253 const timespec* sample_ts,
254 void* arg) {
255 CrasOutputStream* me = static_cast<CrasOutputStream*>(arg);
256 return me->Render(frames, samples, sample_ts);
257 }
258
259 // Static callback for stream errors.
260 int CrasOutputStream::StreamError(cras_client* client,
261 cras_stream_id_t stream_id,
262 int err,
263 void* arg) {
264 CrasOutputStream* me = static_cast<CrasOutputStream*>(arg);
265 me->NotifyStreamError(err);
266 return 0;
267 }
268
269 // Note this is run from a real time thread, so don't waste cycles here.
270 uint32 CrasOutputStream::Render(size_t frames,
271 uint8* buffer,
272 const timespec* sample_ts) {
273 timespec latency_ts = {0, 0};
274
275 // Determine latency and pass that on to the source.
276 cras_client_calc_playback_latency(sample_ts, &latency_ts);
277
278 // Treat negative latency (if we are too slow to render) as 0.
279 uint32 latency_usec;
280 if (latency_ts.tv_sec < 0 || latency_ts.tv_nsec < 0) {
281 latency_usec = 0;
282 } else {
283 latency_usec = (latency_ts.tv_sec * 1000000) +
284 latency_ts.tv_nsec / 1000;
285 }
286
287 uint32 frames_latency = latency_usec * frame_rate_ / 1000000;
288 uint32 bytes_latency = frames_latency * bytes_per_frame_;
289 DCHECK_EQ(frames, static_cast<size_t>(audio_bus_->frames()));
290 int frames_filled = source_callback_->OnMoreData(
291 audio_bus_.get(), AudioBuffersState(0, bytes_latency));
292 // Note: If this ever changes to output raw float the data must be clipped and
293 // sanitized since it may come from an untrusted source such as NaCl.
294 audio_bus_->ToInterleaved(
295 frames_filled, bytes_per_frame_ / num_channels_, buffer);
296 return frames_filled;
297 }
298
299 void CrasOutputStream::NotifyStreamError(int err) {
300 // This will remove the stream from the client.
301 if (state_ == kIsClosed || state_ == kInError)
302 return; // Don't care about error if we aren't using it.
303 TransitionTo(kInError);
304 if (source_callback_)
305 source_callback_->OnError(this);
306 }
307
308 bool CrasOutputStream::CanTransitionTo(InternalState to) {
309 switch (state_) {
310 case kCreated:
311 return to == kIsOpened || to == kIsClosed || to == kInError;
312
313 case kIsOpened:
314 return to == kIsPlaying || to == kIsStopped ||
315 to == kIsClosed || to == kInError;
316
317 case kIsPlaying:
318 return to == kIsPlaying || to == kIsStopped ||
319 to == kIsClosed || to == kInError;
320
321 case kIsStopped:
322 return to == kIsPlaying || to == kIsStopped ||
323 to == kIsClosed || to == kInError;
324
325 case kInError:
326 return to == kIsClosed || to == kInError;
327
328 case kIsClosed:
329 return false;
330 }
331 return false;
332 }
333
334 CrasOutputStream::InternalState
335 CrasOutputStream::TransitionTo(InternalState to) {
336 if (!CanTransitionTo(to)) {
337 state_ = kInError;
338 } else {
339 state_ = to;
340 }
341 return state_;
342 }
343
344 CrasOutputStream::InternalState CrasOutputStream::state() {
345 return state_;
346 }
347
348 } // namespace media
OLDNEW
« no previous file with comments | « media/audio/cras/cras_output.h ('k') | media/audio/cras/cras_output_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698