Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /*
2 * Copyright © 2011 Mozilla Foundation
3 *
4 * This program is made available under an ISC-style license. See the
5 * accompanying file LICENSE for details.
6 */
7 #undef NDEBUG
8 #define __MSVCRT_VERSION__ 0x0700
9 #define WINVER 0x0501
10 #define WIN32_LEAN_AND_MEAN
11 #include <malloc.h>
12 #include <assert.h>
13 #include <windows.h>
14 #include <mmreg.h>
15 #include <mmsystem.h>
16 #include <process.h>
17 #include <stdlib.h>
18 #include "cubeb/cubeb.h"
19 #include "cubeb-internal.h"
21 /* This is missing from the MinGW headers. Use a safe fallback. */
22 #if !defined(MEMORY_ALLOCATION_ALIGNMENT)
23 #define MEMORY_ALLOCATION_ALIGNMENT 16
24 #endif
26 #define CUBEB_STREAM_MAX 32
27 #define NBUFS 4
29 const GUID KSDATAFORMAT_SUBTYPE_PCM =
30 { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
31 const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT =
32 { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
34 struct cubeb_stream_item {
35 SLIST_ENTRY head;
36 cubeb_stream * stream;
37 };
39 static struct cubeb_ops const winmm_ops;
41 struct cubeb {
42 struct cubeb_ops const * ops;
43 HANDLE event;
44 HANDLE thread;
45 int shutdown;
46 PSLIST_HEADER work;
47 CRITICAL_SECTION lock;
48 unsigned int active_streams;
49 unsigned int minimum_latency;
50 };
52 struct cubeb_stream {
53 cubeb * context;
54 cubeb_stream_params params;
55 cubeb_data_callback data_callback;
56 cubeb_state_callback state_callback;
57 void * user_ptr;
58 WAVEHDR buffers[NBUFS];
59 size_t buffer_size;
60 int next_buffer;
61 int free_buffers;
62 int shutdown;
63 int draining;
64 HANDLE event;
65 HWAVEOUT waveout;
66 CRITICAL_SECTION lock;
67 uint64_t written;
68 };
70 static size_t
71 bytes_per_frame(cubeb_stream_params params)
72 {
73 size_t bytes;
75 switch (params.format) {
76 case CUBEB_SAMPLE_S16LE:
77 bytes = sizeof(signed short);
78 break;
79 case CUBEB_SAMPLE_FLOAT32LE:
80 bytes = sizeof(float);
81 break;
82 default:
83 assert(0);
84 }
86 return bytes * params.channels;
87 }
89 static WAVEHDR *
90 winmm_get_next_buffer(cubeb_stream * stm)
91 {
92 WAVEHDR * hdr = NULL;
94 assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
95 hdr = &stm->buffers[stm->next_buffer];
96 assert(hdr->dwFlags & WHDR_PREPARED ||
97 (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
98 stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
99 stm->free_buffers -= 1;
101 return hdr;
102 }
104 static void
105 winmm_refill_stream(cubeb_stream * stm)
106 {
107 WAVEHDR * hdr;
108 long got;
109 long wanted;
110 MMRESULT r;
112 EnterCriticalSection(&stm->lock);
113 stm->free_buffers += 1;
114 assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
116 if (stm->draining) {
117 LeaveCriticalSection(&stm->lock);
118 if (stm->free_buffers == NBUFS) {
119 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
120 }
121 SetEvent(stm->event);
122 return;
123 }
125 if (stm->shutdown) {
126 LeaveCriticalSection(&stm->lock);
127 SetEvent(stm->event);
128 return;
129 }
131 hdr = winmm_get_next_buffer(stm);
133 wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
135 /* It is assumed that the caller is holding this lock. It must be dropped
136 during the callback to avoid deadlocks. */
137 LeaveCriticalSection(&stm->lock);
138 got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted);
139 EnterCriticalSection(&stm->lock);
140 if (got < 0) {
141 LeaveCriticalSection(&stm->lock);
142 /* XXX handle this case */
143 assert(0);
144 return;
145 } else if (got < wanted) {
146 stm->draining = 1;
147 }
148 stm->written += got;
150 assert(hdr->dwFlags & WHDR_PREPARED);
152 hdr->dwBufferLength = got * bytes_per_frame(stm->params);
153 assert(hdr->dwBufferLength <= stm->buffer_size);
155 r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
156 if (r != MMSYSERR_NOERROR) {
157 LeaveCriticalSection(&stm->lock);
158 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
159 return;
160 }
162 LeaveCriticalSection(&stm->lock);
163 }
165 static unsigned __stdcall
166 winmm_buffer_thread(void * user_ptr)
167 {
168 cubeb * ctx = (cubeb *) user_ptr;
169 assert(ctx);
171 for (;;) {
172 DWORD rv;
173 PSLIST_ENTRY item;
175 rv = WaitForSingleObject(ctx->event, INFINITE);
176 assert(rv == WAIT_OBJECT_0);
178 /* Process work items in batches so that a single stream can't
179 starve the others by continuously adding new work to the top of
180 the work item stack. */
181 item = InterlockedFlushSList(ctx->work);
182 while (item != NULL) {
183 PSLIST_ENTRY tmp = item;
184 winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream);
185 item = item->Next;
186 _aligned_free(tmp);
187 }
189 if (ctx->shutdown) {
190 break;
191 }
192 }
194 return 0;
195 }
197 static void CALLBACK
198 winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2)
199 {
200 cubeb_stream * stm = (cubeb_stream *) user_ptr;
201 struct cubeb_stream_item * item;
203 if (msg != WOM_DONE) {
204 return;
205 }
207 item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT);
208 assert(item);
209 item->stream = stm;
210 InterlockedPushEntrySList(stm->context->work, &item->head);
212 SetEvent(stm->context->event);
213 }
215 static unsigned int
216 calculate_minimum_latency(void)
217 {
218 OSVERSIONINFOEX osvi;
219 DWORDLONG mask;
221 /* Running under Terminal Services results in underruns with low latency. */
222 if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) {
223 return 500;
224 }
226 /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */
227 memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
228 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
229 osvi.dwMajorVersion = 6;
230 osvi.dwMinorVersion = 0;
232 mask = 0;
233 VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
234 VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
236 if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) {
237 return 200;
238 }
240 return 100;
241 }
243 static void winmm_destroy(cubeb * ctx);
245 /*static*/ int
246 winmm_init(cubeb ** context, char const * context_name)
247 {
248 cubeb * ctx;
250 assert(context);
251 *context = NULL;
253 ctx = calloc(1, sizeof(*ctx));
254 assert(ctx);
256 ctx->ops = &winmm_ops;
258 ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT);
259 assert(ctx->work);
260 InitializeSListHead(ctx->work);
262 ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
263 if (!ctx->event) {
264 winmm_destroy(ctx);
265 return CUBEB_ERROR;
266 }
268 ctx->thread = (HANDLE) _beginthreadex(NULL, 64 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
269 if (!ctx->thread) {
270 winmm_destroy(ctx);
271 return CUBEB_ERROR;
272 }
274 SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL);
276 InitializeCriticalSection(&ctx->lock);
277 ctx->active_streams = 0;
279 ctx->minimum_latency = calculate_minimum_latency();
281 *context = ctx;
283 return CUBEB_OK;
284 }
286 static char const *
287 winmm_get_backend_id(cubeb * ctx)
288 {
289 return "winmm";
290 }
292 static void
293 winmm_destroy(cubeb * ctx)
294 {
295 DWORD rv;
297 assert(ctx->active_streams == 0);
298 assert(!InterlockedPopEntrySList(ctx->work));
300 DeleteCriticalSection(&ctx->lock);
302 if (ctx->thread) {
303 ctx->shutdown = 1;
304 SetEvent(ctx->event);
305 rv = WaitForSingleObject(ctx->thread, INFINITE);
306 assert(rv == WAIT_OBJECT_0);
307 CloseHandle(ctx->thread);
308 }
310 if (ctx->event) {
311 CloseHandle(ctx->event);
312 }
314 _aligned_free(ctx->work);
316 free(ctx);
317 }
319 static void winmm_stream_destroy(cubeb_stream * stm);
321 static int
322 winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
323 cubeb_stream_params stream_params, unsigned int latency,
324 cubeb_data_callback data_callback,
325 cubeb_state_callback state_callback,
326 void * user_ptr)
327 {
328 MMRESULT r;
329 WAVEFORMATEXTENSIBLE wfx;
330 cubeb_stream * stm;
331 int i;
332 size_t bufsz;
334 assert(context);
335 assert(stream);
337 *stream = NULL;
339 memset(&wfx, 0, sizeof(wfx));
340 if (stream_params.channels > 2) {
341 wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
342 wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
343 } else {
344 wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
345 if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE) {
346 wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
347 }
348 wfx.Format.cbSize = 0;
349 }
350 wfx.Format.nChannels = stream_params.channels;
351 wfx.Format.nSamplesPerSec = stream_params.rate;
353 /* XXX fix channel mappings */
354 wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
356 switch (stream_params.format) {
357 case CUBEB_SAMPLE_S16LE:
358 wfx.Format.wBitsPerSample = 16;
359 wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
360 break;
361 case CUBEB_SAMPLE_FLOAT32LE:
362 wfx.Format.wBitsPerSample = 32;
363 wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
364 break;
365 default:
366 return CUBEB_ERROR_INVALID_FORMAT;
367 }
369 wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
370 wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
371 wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
373 EnterCriticalSection(&context->lock);
374 /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when
375 many streams are active at once, a subset of them will not consume (via
376 playback) or release (via waveOutReset) their buffers. */
377 if (context->active_streams >= CUBEB_STREAM_MAX) {
378 LeaveCriticalSection(&context->lock);
379 return CUBEB_ERROR;
380 }
381 context->active_streams += 1;
382 LeaveCriticalSection(&context->lock);
384 stm = calloc(1, sizeof(*stm));
385 assert(stm);
387 stm->context = context;
389 stm->params = stream_params;
391 stm->data_callback = data_callback;
392 stm->state_callback = state_callback;
393 stm->user_ptr = user_ptr;
394 stm->written = 0;
396 if (latency < context->minimum_latency) {
397 latency = context->minimum_latency;
398 }
400 bufsz = (size_t) (stm->params.rate / 1000.0 * latency * bytes_per_frame(stm->params) / NBUFS);
401 if (bufsz % bytes_per_frame(stm->params) != 0) {
402 bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
403 }
404 assert(bufsz % bytes_per_frame(stm->params) == 0);
406 stm->buffer_size = bufsz;
408 InitializeCriticalSection(&stm->lock);
410 stm->event = CreateEvent(NULL, FALSE, FALSE, NULL);
411 if (!stm->event) {
412 winmm_stream_destroy(stm);
413 return CUBEB_ERROR;
414 }
416 /* winmm_buffer_callback will be called during waveOutOpen, so all
417 other initialization must be complete before calling it. */
418 r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
419 (DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm,
420 CALLBACK_FUNCTION);
421 if (r != MMSYSERR_NOERROR) {
422 winmm_stream_destroy(stm);
423 return CUBEB_ERROR;
424 }
426 r = waveOutPause(stm->waveout);
427 if (r != MMSYSERR_NOERROR) {
428 winmm_stream_destroy(stm);
429 return CUBEB_ERROR;
430 }
432 for (i = 0; i < NBUFS; ++i) {
433 WAVEHDR * hdr = &stm->buffers[i];
435 hdr->lpData = calloc(1, bufsz);
436 assert(hdr->lpData);
437 hdr->dwBufferLength = bufsz;
438 hdr->dwFlags = 0;
440 r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr));
441 if (r != MMSYSERR_NOERROR) {
442 winmm_stream_destroy(stm);
443 return CUBEB_ERROR;
444 }
446 winmm_refill_stream(stm);
447 }
449 *stream = stm;
451 return CUBEB_OK;
452 }
454 static void
455 winmm_stream_destroy(cubeb_stream * stm)
456 {
457 DWORD rv;
458 int i;
459 int enqueued;
461 if (stm->waveout) {
462 EnterCriticalSection(&stm->lock);
463 stm->shutdown = 1;
465 waveOutReset(stm->waveout);
467 enqueued = NBUFS - stm->free_buffers;
468 LeaveCriticalSection(&stm->lock);
470 /* Wait for all blocks to complete. */
471 while (enqueued > 0) {
472 rv = WaitForSingleObject(stm->event, INFINITE);
473 assert(rv == WAIT_OBJECT_0);
475 EnterCriticalSection(&stm->lock);
476 enqueued = NBUFS - stm->free_buffers;
477 LeaveCriticalSection(&stm->lock);
478 }
480 EnterCriticalSection(&stm->lock);
482 for (i = 0; i < NBUFS; ++i) {
483 if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
484 waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
485 }
486 }
488 waveOutClose(stm->waveout);
490 LeaveCriticalSection(&stm->lock);
491 }
493 if (stm->event) {
494 CloseHandle(stm->event);
495 }
497 DeleteCriticalSection(&stm->lock);
499 for (i = 0; i < NBUFS; ++i) {
500 free(stm->buffers[i].lpData);
501 }
503 EnterCriticalSection(&stm->context->lock);
504 assert(stm->context->active_streams >= 1);
505 stm->context->active_streams -= 1;
506 LeaveCriticalSection(&stm->context->lock);
508 free(stm);
509 }
511 static int
512 winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
513 {
514 assert(ctx && max_channels);
516 /* We don't support more than two channels in this backend. */
517 *max_channels = 2;
519 return CUBEB_OK;
520 }
522 static int
523 winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
524 {
525 // 100ms minimum, if we are not in a bizarre configuration.
526 *latency = ctx->minimum_latency;
528 return CUBEB_OK;
529 }
531 static int
532 winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
533 {
534 WAVEOUTCAPS woc;
535 MMRESULT r;
537 r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
538 if (r != MMSYSERR_NOERROR) {
539 return CUBEB_ERROR;
540 }
542 /* Check if we support 48kHz, but not 44.1kHz. */
543 if (!(woc.dwFormats & WAVE_FORMAT_4S16) &&
544 woc.dwFormats & WAVE_FORMAT_48S16) {
545 *rate = 48000;
546 return CUBEB_OK;
547 }
548 /* Prefer 44.1kHz between 44.1kHz and 48kHz. */
549 *rate = 44100;
551 return CUBEB_OK;
552 }
554 static int
555 winmm_stream_start(cubeb_stream * stm)
556 {
557 MMRESULT r;
559 EnterCriticalSection(&stm->lock);
560 r = waveOutRestart(stm->waveout);
561 LeaveCriticalSection(&stm->lock);
563 if (r != MMSYSERR_NOERROR) {
564 return CUBEB_ERROR;
565 }
567 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
569 return CUBEB_OK;
570 }
572 static int
573 winmm_stream_stop(cubeb_stream * stm)
574 {
575 MMRESULT r;
577 EnterCriticalSection(&stm->lock);
578 r = waveOutPause(stm->waveout);
579 LeaveCriticalSection(&stm->lock);
581 if (r != MMSYSERR_NOERROR) {
582 return CUBEB_ERROR;
583 }
585 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
587 return CUBEB_OK;
588 }
590 static int
591 winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
592 {
593 MMRESULT r;
594 MMTIME time;
596 EnterCriticalSection(&stm->lock);
597 time.wType = TIME_SAMPLES;
598 r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
599 LeaveCriticalSection(&stm->lock);
601 if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
602 return CUBEB_ERROR;
603 }
605 *position = time.u.sample;
607 return CUBEB_OK;
608 }
610 static int
611 winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
612 {
613 MMRESULT r;
614 MMTIME time;
615 uint64_t written;
617 EnterCriticalSection(&stm->lock);
618 time.wType = TIME_SAMPLES;
619 r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
620 written = stm->written;
621 LeaveCriticalSection(&stm->lock);
623 *latency = written - time.u.sample;
625 return CUBEB_OK;
626 }
628 static struct cubeb_ops const winmm_ops = {
629 /*.init =*/ winmm_init,
630 /*.get_backend_id =*/ winmm_get_backend_id,
631 /*.get_max_channel_count=*/ winmm_get_max_channel_count,
632 /*.get_min_latency=*/ winmm_get_min_latency,
633 /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
634 /*.destroy =*/ winmm_destroy,
635 /*.stream_init =*/ winmm_stream_init,
636 /*.stream_destroy =*/ winmm_stream_destroy,
637 /*.stream_start =*/ winmm_stream_start,
638 /*.stream_stop =*/ winmm_stream_stop,
639 /*.stream_get_position =*/ winmm_stream_get_position,
640 /*.stream_get_latency = */ winmm_stream_get_latency
641 };