TEST(cubeb, drain) { int r; cubeb * ctx; cubeb_stream * stream; cubeb_stream_params params; uint64_t position; delay_callback = 0; total_frames_written = 0; r = common_init(&ctx, "test_sanity"); ASSERT_EQ(r, CUBEB_OK); ASSERT_NE(ctx, nullptr); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; params.layout = STREAM_LAYOUT; r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_drain_data_callback, test_drain_state_callback, &dummy); ASSERT_EQ(r, CUBEB_OK); ASSERT_NE(stream, nullptr); r = cubeb_stream_start(stream); ASSERT_EQ(r, CUBEB_OK); delay(500); do_drain = 1; for (;;) { r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); if (got_drain) { break; } else { ASSERT_LE(position, total_frames_written.load()); } delay(500); } r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); ASSERT_TRUE(got_drain); // Really, we should be able to rely on position reaching our final written frame, but // for now let's make sure it doesn't continue beyond that point. //ASSERT_LE(position, total_frames_written.load()); cubeb_stream_destroy(stream); cubeb_destroy(ctx); got_drain = 0; do_drain = 0; }
static void test_drain(void) { int r; cubeb * ctx; cubeb_stream * stream; cubeb_stream_params params; uint64_t position; BEGIN_TEST; total_frames_written = 0; r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, test_drain_data_callback, test_drain_state_callback, &dummy); assert(r == 0 && stream); r = cubeb_stream_start(stream); assert(r == 0); delay(500); do_drain = 1; for (;;) { r = cubeb_stream_get_position(stream, &position); assert(r == 0); if (got_drain) { break; } else { assert(position <= total_frames_written); } delay(500); } r = cubeb_stream_get_position(stream, &position); assert(r == 0); assert(got_drain); // Really, we should be able to rely on position reaching our final written frame, but // for now let's make sure it doesn't continue beyond that point. //assert(position <= total_frames_written); cubeb_stream_destroy(stream); cubeb_destroy(ctx); END_TEST; }
int64_t BufferedAudioStream::GetPositionInFramesUnlocked() { mMonitor.AssertCurrentThreadOwns(); if (!mCubebStream || mState == ERRORED) { return -1; } uint64_t position = 0; { MonitorAutoUnlock mon(mMonitor); if (cubeb_stream_get_position(mCubebStream, &position) != CUBEB_OK) { return -1; } } // Adjust the reported position by the number of silent frames written // during stream underruns. uint64_t adjustedPosition = 0; if (position >= mLostFrames) { adjustedPosition = position - mLostFrames; } return std::min<uint64_t>(adjustedPosition, INT64_MAX); }
static void test_basic_stream_operations(void) { int r; cubeb * ctx; cubeb_stream * stream; cubeb_stream_params params; uint64_t position; BEGIN_TEST r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0 && stream); /* position and volume before stream has started */ r = cubeb_stream_get_position(stream, &position); assert(r == 0 && position == 0); r = cubeb_stream_start(stream); assert(r == 0); /* position and volume after while stream running */ r = cubeb_stream_get_position(stream, &position); assert(r == 0); r = cubeb_stream_stop(stream); assert(r == 0); /* position and volume after stream has stopped */ r = cubeb_stream_get_position(stream, &position); assert(r == 0); cubeb_stream_destroy(stream); cubeb_destroy(ctx); END_TEST }
TEST(cubeb, basic_stream_operations) { int r; cubeb * ctx; cubeb_stream * stream; cubeb_stream_params params; uint64_t position; r = common_init(&ctx, "test_sanity"); ASSERT_EQ(r, CUBEB_OK); ASSERT_NE(ctx, nullptr); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; params.layout = STREAM_LAYOUT; r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); ASSERT_EQ(r, CUBEB_OK); ASSERT_NE(stream, nullptr); /* position and volume before stream has started */ r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); ASSERT_EQ(position, 0u); r = cubeb_stream_start(stream); ASSERT_EQ(r, CUBEB_OK); /* position and volume after while stream running */ r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); r = cubeb_stream_stop(stream); ASSERT_EQ(r, CUBEB_OK); /* position and volume after stream has stopped */ r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); cubeb_stream_destroy(stream); cubeb_destroy(ctx); }
GraphTime AudioCallbackDriver::GetCurrentTime() { uint64_t position = 0; if (cubeb_stream_get_position(mAudioStream, &position) != CUBEB_OK) { NS_WARNING("Could not get current time from cubeb."); } return mSampleRate * position; }
int64_t AudioStream::GetPositionInFramesUnlocked() { mMonitor.AssertCurrentThreadOwns(); if (!mCubebStream || mState == ERRORED) { return -1; } uint64_t position = 0; { MonitorAutoUnlock mon(mMonitor); if (cubeb_stream_get_position(mCubebStream, &position) != CUBEB_OK) { return -1; } } // Adjust the reported position by the number of silent frames written // during stream underruns. // Since frames sent to DataCallback is not consumed by the backend immediately, // it will be an over adjustment if we return |position - mLostFramesPast - mLostFramesLast|. // On the other hand, we need to keep the whole history of frames sent to DataCallback // in order to adjust position correctly which will require more storage. // We choose a simple way to store the history where |mWrittenFramesPast| and // |mLostFramesPast| are the sum of frames from 1th to |N-1|th callbacks, and // |mWrittenFramesLast| and |mLostFramesLast| represent the frames sent in last callback. // When |position| lies in // [mWrittenFramesPast+mLostFramesPast, mWrittenFramesPast+mLostFramesPast+mWrittenFramesLast+mLostFramesLast], // we will be able to adjust position precisely which should be the major case. // If |position| falls in [0, mWrittenFramesPast+mLostFramesPast), there will be an // error in the adjustment. However that is fine as long as we can ensure the // adjusted position is mono-increasing to avoid audio clock going backward. uint64_t adjustedPosition = 0; if (position <= mWrittenFramesPast) { adjustedPosition = position; } else if (position <= mWrittenFramesPast + mLostFramesPast) { adjustedPosition = mWrittenFramesPast; } else if (position <= mWrittenFramesPast + mLostFramesPast + mWrittenFramesLast) { adjustedPosition = position - mLostFramesPast; } else { adjustedPosition = mWrittenFramesPast + mWrittenFramesLast; } return std::min<uint64_t>(adjustedPosition, INT64_MAX); }
int64_t AudioStream::GetPositionInFramesUnlocked() { mMonitor.AssertCurrentThreadOwns(); if (!mCubebStream || mState == ERRORED) { return -1; } uint64_t position = 0; { MonitorAutoUnlock mon(mMonitor); if (cubeb_stream_get_position(mCubebStream.get(), &position) != CUBEB_OK) { return -1; } } return std::min<uint64_t>(position, INT64_MAX); }
int64_t AudioStream::GetPositionInFramesUnlocked() { mMonitor.AssertCurrentThreadOwns(); if (!mCubebStream || mState == ERRORED) { return -1; } uint64_t position = 0; { MonitorAutoUnlock mon(mMonitor); if (cubeb_stream_get_position(mCubebStream.get(), &position) != CUBEB_OK) { return -1; } } MOZ_ASSERT(position >= mLastGoodPosition, "cubeb position shouldn't go backward"); // This error handling/recovery keeps us in good shape in release build. if (position >= mLastGoodPosition) { mLastGoodPosition = position; } return std::min<uint64_t>(mLastGoodPosition, INT64_MAX); }
static void test_drain(void) { int r; cubeb * ctx; cubeb_stream * stream; cubeb_stream_params params; uint64_t position; BEGIN_TEST total_frames_written = 0; r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, test_drain_data_callback, test_drain_state_callback, &dummy); assert(r == 0 && stream); r = cubeb_stream_start(stream); assert(r == 0); delay(500); do_drain = 1; for (;;) { r = cubeb_stream_get_position(stream, &position); assert(r == 0); if (got_drain) { break; } else { uint32_t i, skip = 0; /* Latency passed to cubeb_stream_init is not really honored on OSX, win32/winmm and android, skip this test. */ const char * backend_id = cubeb_get_backend_id(ctx); const char * latency_not_honored_bakends[] = { "audiounit", "winmm", "audiotrack", "opensl" }; for (i = 0; i < ARRAY_LENGTH(latency_not_honored_bakends); i++) { if (!strcmp(backend_id, latency_not_honored_bakends[i])) { skip = 1; } } if (!skip) { /* Position should roughly be equal to the number of written frames. We * need to take the latency into account. */ int latency = (STREAM_LATENCY * STREAM_RATE) / 1000; assert(position + latency <= total_frames_written); } } delay(500); } r = cubeb_stream_get_position(stream, &position); assert(r == 0); assert(got_drain); // Disabled due to failures in the ALSA backend. //assert(position == total_frames_written); cubeb_stream_destroy(stream); cubeb_destroy(ctx); END_TEST }
static void test_stream_position(void) { int i; int r; cubeb * ctx; cubeb_stream * stream; cubeb_stream_params params; uint64_t position, last_position; BEGIN_TEST total_frames_written = 0; r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); assert(r == 0 && stream); /* stream position should not advance before starting playback */ r = cubeb_stream_get_position(stream, &position); assert(r == 0 && position == 0); delay(500); r = cubeb_stream_get_position(stream, &position); assert(r == 0 && position == 0); /* stream position should advance during playback */ r = cubeb_stream_start(stream); assert(r == 0); /* XXX let start happen */ delay(500); /* stream should have prefilled */ assert(total_frames_written > 0); r = cubeb_stream_get_position(stream, &position); assert(r == 0); last_position = position; delay(500); r = cubeb_stream_get_position(stream, &position); assert(r == 0); assert(position >= last_position); last_position = position; /* stream position should not exceed total frames written */ for (i = 0; i < 5; ++i) { r = cubeb_stream_get_position(stream, &position); assert(r == 0); assert(position >= last_position); assert(position <= total_frames_written); last_position = position; delay(500); } assert(last_position != 0); /* stream position should not advance after stopping playback */ r = cubeb_stream_stop(stream); assert(r == 0); /* XXX allow stream to settle */ delay(500); r = cubeb_stream_get_position(stream, &position); assert(r == 0); last_position = position; delay(500); r = cubeb_stream_get_position(stream, &position); assert(r == 0); assert(position == last_position); cubeb_stream_destroy(stream); cubeb_destroy(ctx); END_TEST }
TEST(cubeb, stream_position) { size_t i; int r; cubeb * ctx; cubeb_stream * stream; cubeb_stream_params params; uint64_t position, last_position; total_frames_written = 0; r = common_init(&ctx, "test_sanity"); ASSERT_EQ(r, CUBEB_OK); ASSERT_NE(ctx, nullptr); params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; params.layout = STREAM_LAYOUT; r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); ASSERT_EQ(r, CUBEB_OK); ASSERT_NE(stream, nullptr); /* stream position should not advance before starting playback */ r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); ASSERT_EQ(position, 0u); delay(500); r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); ASSERT_EQ(position, 0u); /* stream position should advance during playback */ r = cubeb_stream_start(stream); ASSERT_EQ(r, CUBEB_OK); /* XXX let start happen */ delay(500); /* stream should have prefilled */ ASSERT_TRUE(total_frames_written.load() > 0); r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); last_position = position; delay(500); r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); ASSERT_GE(position, last_position); last_position = position; /* stream position should not exceed total frames written */ for (i = 0; i < 5; ++i) { r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); ASSERT_GE(position, last_position); ASSERT_LE(position, total_frames_written.load()); last_position = position; delay(500); } /* test that the position is valid even when starting and * stopping the stream. */ for (i = 0; i < 5; ++i) { r = cubeb_stream_stop(stream); ASSERT_EQ(r, CUBEB_OK); r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); ASSERT_TRUE(last_position < position); last_position = position; delay(500); r = cubeb_stream_start(stream); ASSERT_EQ(r, CUBEB_OK); delay(500); } ASSERT_NE(last_position, 0u); /* stream position should not advance after stopping playback */ r = cubeb_stream_stop(stream); ASSERT_EQ(r, CUBEB_OK); /* XXX allow stream to settle */ delay(500); r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); last_position = position; delay(500); r = cubeb_stream_get_position(stream, &position); ASSERT_EQ(r, CUBEB_OK); ASSERT_EQ(position, last_position); cubeb_stream_destroy(stream); cubeb_destroy(ctx); }
static int directsound_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, cubeb_stream_params stream_params, unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { struct cubeb_list_node * node; assert(context); *stream = NULL; /* create primary buffer */ DSBUFFERDESC bd; bd.dwSize = sizeof(DSBUFFERDESC); bd.dwFlags = DSBCAPS_PRIMARYBUFFER; bd.dwBufferBytes = 0; bd.dwReserved = 0; bd.lpwfxFormat = NULL; bd.guid3DAlgorithm = DS3DALG_DEFAULT; LPDIRECTSOUNDBUFFER primary; if (FAILED(context->dsound->CreateSoundBuffer(&bd, &primary, NULL))) { return 1; } WAVEFORMATEXTENSIBLE wfx; wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfx.Format.nChannels = stream_params.channels; wfx.Format.nSamplesPerSec = stream_params.rate; wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format); /* XXX fix channel mappings */ wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; switch (stream_params.format) { case CUBEB_SAMPLE_S16LE: wfx.Format.wBitsPerSample = 16; wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case CUBEB_SAMPLE_FLOAT32LE: wfx.Format.wBitsPerSample = 32; wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break; default: return CUBEB_ERROR_INVALID_FORMAT; } wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8; wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample; if (FAILED(primary->SetFormat((LPWAVEFORMATEX) &wfx))) { /* XXX free primary */ return CUBEB_ERROR; } primary->Release(); cubeb_stream * stm = (cubeb_stream *) calloc(1, sizeof(*stm)); assert(stm); stm->context = context; stm->params = stream_params; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; InitializeCriticalSection(&stm->lock); /* create secondary buffer */ bd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY; bd.dwBufferBytes = (DWORD) (wfx.Format.nSamplesPerSec / 1000.0 * latency * bytes_per_frame(stream_params)); if (bd.dwBufferBytes % bytes_per_frame(stream_params) != 0) { bd.dwBufferBytes += bytes_per_frame(stream_params) - (bd.dwBufferBytes % bytes_per_frame(stream_params)); } bd.lpwfxFormat = (LPWAVEFORMATEX) &wfx; if (FAILED(context->dsound->CreateSoundBuffer(&bd, &stm->buffer, NULL))) { return CUBEB_ERROR; } stm->buffer_size = bd.dwBufferBytes; LPDIRECTSOUNDNOTIFY notify; if (stm->buffer->QueryInterface(IID_IDirectSoundNotify, (LPVOID *) ¬ify) != DS_OK) { /* XXX free resources */ return CUBEB_ERROR; } DSBPOSITIONNOTIFY note[3]; for (int i = 0; i < 3; ++i) { note[i].dwOffset = (stm->buffer_size / 4) * i; note[i].hEventNotify = context->streams_event; } if (notify->SetNotificationPositions(3, note) != DS_OK) { /* XXX free resources */ return CUBEB_ERROR; } notify->Release(); refill_stream(stm, 1); /* XXX remove this, just a test that double refill does not overwrite existing data */ refill_stream(stm, 0); uint64_t pos; cubeb_stream_get_position(stm, &pos); stm->node = (struct cubeb_list_node *) calloc(1, sizeof(*node)); stm->node->stream = stm; EnterCriticalSection(&context->lock); if (!context->streams) { context->streams = stm->node; } else { node = context->streams; while (node->next) { node = node->next; } node->next = stm->node; stm->node->prev = node; } LeaveCriticalSection(&context->lock); SetEvent(context->streams_event); *stream = stm; return CUBEB_OK; }