Victory! With these changes, playback is nearly perfect under BeOS (tested with

C-Media based sound card). In Haiku, I could test with HD Audio, and playback
has clicks. It doesn't seem to have to do with the "drift", which is now
correctly published, I am not sure what exactly is causing it, I would like to
test on different hardware yet.
* I have modified the buffering policy (4 will give about 2048 bytes internal
  OSS buffer), which decreases the latency of the node to an acceptable value.
* I completely replaced the timesource publishing algo to be more reliable.
* Removed now unnecessary methods from OpenSoundDeviceEngine.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@26059 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus
2008-06-21 12:14:26 +00:00
parent 1f5afb2e36
commit d3ebd7843a
5 changed files with 632 additions and 561 deletions
@@ -6,6 +6,7 @@
*/ */
#include "OpenSoundDeviceEngine.h" #include "OpenSoundDeviceEngine.h"
#include "debug.h" #include "debug.h"
#include "driver_io.h" #include "driver_io.h"
#include <MediaDefs.h> #include <MediaDefs.h>
@@ -13,6 +14,8 @@
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include "SupportFunctions.h"
OpenSoundDeviceEngine::~OpenSoundDeviceEngine() OpenSoundDeviceEngine::~OpenSoundDeviceEngine()
{ {
CALLED(); CALLED();
@@ -27,8 +30,7 @@ OpenSoundDeviceEngine::OpenSoundDeviceEngine(oss_audioinfo *info)
, fOpenMode(0) , fOpenMode(0)
, fFD(-1) , fFD(-1)
, fMediaFormat() , fMediaFormat()
, fPlayedFramesCount(0LL) , fDriverBufferSize(0)
, fPlayedRealTime(0LL)
{ {
CALLED(); CALLED();
fInitCheckStatus = B_NO_INIT; fInitCheckStatus = B_NO_INIT;
@@ -93,7 +95,19 @@ status_t OpenSoundDeviceEngine::Open(int mode)
Close(); Close();
return EIO; return EIO;
} }
#if 1
// set latency policy = fragment size (4 means 2048 bytes driver buffer
// in my tests)
// XXX: BParameter?
v = 4;
if (ioctl(fFD, SNDCTL_DSP_POLICY, &v, sizeof(int)) < 0) {
if (errno != EIO && errno != EINVAL) {
fInitCheckStatus = errno;
Close();
return EIO;
}
// TODO: use this older API as fallback:
#if 0
// set fragments // set fragments
v = 0x7fff0000 | 0x000b; // unlimited * 2048 v = 0x7fff0000 | 0x000b; // unlimited * 2048
if (ioctl(fFD, SNDCTL_DSP_SETFRAGMENT, &v, sizeof(int)) < 0) { if (ioctl(fFD, SNDCTL_DSP_SETFRAGMENT, &v, sizeof(int)) < 0) {
@@ -102,20 +116,10 @@ status_t OpenSoundDeviceEngine::Open(int mode)
return EIO; return EIO;
} }
#endif #endif
#if 0
// set latency policy = fragment size
// XXX: BParameter?
v = 0;
if (ioctl(fFD, SNDCTL_DSP_POLICY, &v, sizeof(int)) < 0) {
fInitCheckStatus = errno;
Close();
return EIO;
} }
#endif
fPlayedFramesCount = 0LL; fDriverBufferSize = 2048;
fPlayedRealTime = system_time(); // preliminary, adjusted in AcceptFormat()
return B_OK; return B_OK;
} }
@@ -128,8 +132,6 @@ status_t OpenSoundDeviceEngine::Close(void)
fFD = -1; fFD = -1;
fOpenMode = 0; fOpenMode = 0;
fMediaFormat = media_format(); fMediaFormat = media_format();
fPlayedFramesCount = 0LL;
fPlayedRealTime = 0LL;
return B_OK; return B_OK;
} }
@@ -145,50 +147,32 @@ ssize_t OpenSoundDeviceEngine::Read(void *buffer, size_t size)
} }
ssize_t OpenSoundDeviceEngine::Write(const void *buffer, size_t size) ssize_t
OpenSoundDeviceEngine::Write(const void *buffer, size_t size)
{ {
ssize_t done;
int v;
CALLED(); CALLED();
ASSERT(size > 0); ASSERT(size > 0);
done = write(fFD, buffer, size);
ssize_t done = write(fFD, buffer, size);
if (done < 0) if (done < 0)
return errno; return errno;
switch (fMediaFormat.type) {
case B_MEDIA_RAW_AUDIO:
fPlayedFramesCount += done / (fMediaFormat.u.raw_audio.channel_count
* (fMediaFormat.AudioFormat() & media_raw_audio_format::B_AUDIO_SIZE_MASK));
/* fPlayedRealTime = system_time();
v = 0;
if (ioctl(fFD, SNDCTL_DSP_GETODELAY, &v, sizeof(int)) > -1) {
bigtime_t delay = (bigtime_t)v * 1000000LL
/ (fMediaFormat.u.raw_audio.channel_count * fMediaFormat.u.raw_audio.frame_rate
* (fMediaFormat.AudioFormat() & media_raw_audio_format::B_AUDIO_SIZE_MASK));
fPlayedRealTime += delay;
PRINT(("********************************* v = %d, delay %Ld\n", v, delay));
}*/
// PRINT(("OpenSoundDeviceEngine::%s: wrote %d, played %Ld frames"/*", realtime %Ld"*/"\n", __FUNCTION__, done, fPlayedFramesCount/*, fPlayedRealTime*/));
break;
case B_MEDIA_ENCODED_AUDIO:
//XXX: WRITEME! -- bitrate ?
break;
default:
return EINVAL;
}
return done; return done;
} }
status_t OpenSoundDeviceEngine::UpdateInfo(void) status_t
OpenSoundDeviceEngine::UpdateInfo()
{ {
status_t err;
CALLED(); CALLED();
if (fFD < 0) if (fFD < 0)
return ENODEV; return ENODEV;
if (ioctl(fFD, SNDCTL_ENGINEINFO, &fAudioInfo, sizeof(oss_audioinfo)) < 0) { if (ioctl(fFD, SNDCTL_ENGINEINFO, &fAudioInfo, sizeof(oss_audioinfo)) < 0)
return errno; return errno;
}
return B_OK; return B_OK;
} }
@@ -196,18 +180,15 @@ status_t OpenSoundDeviceEngine::UpdateInfo(void)
bigtime_t bigtime_t
OpenSoundDeviceEngine::PlaybackLatency() OpenSoundDeviceEngine::PlaybackLatency()
{ {
bigtime_t latency; bigtime_t latency = time_for_buffer(fDriverBufferSize, fMediaFormat);
int delay; bigtime_t cardLatency = CardLatency();
delay = GetODelay(); if (cardLatency == 0) {
delay = 0; //XXX // that's unrealistic, take matters into own hands
latency = (bigtime_t)((double)delay * 1000000LL cardLatency = latency / 3;
/ (fMediaFormat.u.raw_audio.channel_count }
* fMediaFormat.u.raw_audio.frame_rate latency += cardLatency;
* (fMediaFormat.AudioFormat() // PRINT(("PlaybackLatency: odelay %d latency %Ld card %Ld\n",
& media_raw_audio_format::B_AUDIO_SIZE_MASK))); // fDriverBufferSize, latency, CardLatency()));
PRINT(("PlaybackLatency: odelay %d latency %Ld card %Ld\n", delay, latency,
CardLatency()));
latency += CardLatency();
return latency; return latency;
} }
@@ -365,35 +346,42 @@ OpenSoundDeviceEngine::GetCurrentIPtr(int32 *fifoed, oss_count_t *info)
int64 int64
OpenSoundDeviceEngine::GetCurrentOPtr(int32 *fifoed, oss_count_t *info) OpenSoundDeviceEngine::GetCurrentOPtr(int32* fifoed, size_t* fragmentPos)
{ {
oss_count_t ocount;
count_info cinfo;
CALLED(); CALLED();
if (!info)
info = &ocount; if (!(fOpenMode & OPEN_WRITE)) {
memset(info, 0, sizeof(oss_count_t)); if (fifoed != NULL)
if (!(fOpenMode & OPEN_WRITE)) *fifoed = 0;
return 0; return 0;
if (ioctl(fFD, SNDCTL_DSP_CURRENT_OPTR, info, sizeof(oss_count_t)) < 0) { }
oss_count_t info;
memset(&info, 0, sizeof(oss_count_t));
if (ioctl(fFD, SNDCTL_DSP_CURRENT_OPTR, &info, sizeof(oss_count_t)) < 0) {
PRINT(("OpenSoundDeviceEngine::%s: %s: %s\n", PRINT(("OpenSoundDeviceEngine::%s: %s: %s\n",
__FUNCTION__, "SNDCTL_DSP_CURRENT_OPTR", strerror(errno))); __FUNCTION__, "SNDCTL_DSP_CURRENT_OPTR", strerror(errno)));
//return EIO;
// fallback: try GET*PTR return 0;
}
if (fragmentPos != NULL) {
count_info cinfo;
if (ioctl(fFD, SNDCTL_DSP_GETOPTR, &cinfo, sizeof(count_info)) < 0) { if (ioctl(fFD, SNDCTL_DSP_GETOPTR, &cinfo, sizeof(count_info)) < 0) {
PRINT(("OpenSoundDeviceEngine::%s: %s: %s\n", PRINT(("OpenSoundDeviceEngine::%s: %s: %s\n",
__FUNCTION__, "SNDCTL_DSP_GETOPTR", strerror(errno))); __FUNCTION__, "SNDCTL_DSP_GETOPTR", strerror(errno)));
return 0; return 0;
} }
// it's probably wrong... *fragmentPos = cinfo.ptr;
info->samples = cinfo.bytes / (fMediaFormat.u.raw_audio.channel_count
* (fMediaFormat.AudioFormat() & media_raw_audio_format::B_AUDIO_SIZE_MASK));
info->fifo_samples = 0;
} }
//PRINT(("OpenSoundDeviceEngine::%s: OPTR: { samples=%Ld, fifo_samples=%d }\n", __FUNCTION__, info->samples, info->fifo_samples));
if (fifoed) // PRINT(("OpenSoundDeviceEngine::%s: OPTR: { samples=%Ld, "
*fifoed = info->fifo_samples; // "fifo_samples=%d }\n", __FUNCTION__, info->samples,
return info->samples; // info->fifo_samples));
if (fifoed != NULL)
*fifoed = info.fifo_samples;
return info.samples;
} }
@@ -433,14 +421,10 @@ OpenSoundDeviceEngine::GetOUnderruns()
} }
int OpenSoundDeviceEngine::GetODelay(void) size_t
OpenSoundDeviceEngine::DriverBufferSize() const
{ {
//CALLED(); return fDriverBufferSize;
int v = 1;
if (ioctl(fFD, SNDCTL_DSP_GETODELAY, &v, sizeof(int)) < 0) {
return 0;
}
return v;
} }
@@ -464,43 +448,9 @@ status_t OpenSoundDeviceEngine::StartRecording(void)
} }
int64 OpenSoundDeviceEngine::PlayedFramesCount(void) status_t
{ OpenSoundDeviceEngine::WildcardFormatFor(int fmt, media_format &format,
int64 played; bool rec)
int32 fifoed;
played = GetCurrentOPtr(&fifoed);
//played += fifoed;
//return played;
fPlayedFramesCount = played - fifoed;
return fPlayedFramesCount;//XXX
return fPlayedFramesCount - (GetODelay() / (fMediaFormat.u.raw_audio.channel_count
* (fMediaFormat.AudioFormat() & media_raw_audio_format::B_AUDIO_SIZE_MASK)));
return fPlayedFramesCount - (GetODelay() / (/*fMediaFormat.u.raw_audio.channel_count
* */(fMediaFormat.AudioFormat() & media_raw_audio_format::B_AUDIO_SIZE_MASK)));
//return fPlayedFramesCount;
}
bigtime_t OpenSoundDeviceEngine::PlayedRealTime(void)
{
//CALLED();
bigtime_t playedRealTime = system_time();
return playedRealTime;//XXX
int v = 1;
if (ioctl(fFD, SNDCTL_DSP_GETODELAY, &v, sizeof(int)) < 0) {
return playedRealTime;
}
bigtime_t delay = (bigtime_t)(v * 1000000LL
/ (fMediaFormat.u.raw_audio.channel_count * fMediaFormat.u.raw_audio.frame_rate
* (fMediaFormat.AudioFormat() & media_raw_audio_format::B_AUDIO_SIZE_MASK)));
playedRealTime += delay;
//PRINT(("********************************* v = %d, delay %Ld\n", v, delay));
// playedRealTime-=41000;
return playedRealTime;
}
status_t OpenSoundDeviceEngine::WildcardFormatFor(int fmt, media_format &format, bool rec)
{ {
status_t err; status_t err;
CALLED(); CALLED();
@@ -638,32 +588,20 @@ status_t OpenSoundDeviceEngine::AcceptFormatFor(int fmt, media_format &format, b
return err; return err;
} }
// retrieve the driver buffer size (it's important to do this
// after all the other setup, since OSS may have adjusted it, and
#if 0 // also weird things happen if this ioctl() is done before other
raw.buffer_size = DEFAULT_BUFFER_SIZE // setup itctl()s)
* (raw.format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
* raw.channel_count;
#endif
audio_buf_info abinfo; audio_buf_info abinfo;
if (ioctl(fFD, rec?SNDCTL_DSP_GETISPACE:SNDCTL_DSP_GETOSPACE, &abinfo, sizeof(abinfo)) < 0) { memset(&abinfo, 0, sizeof(audio_buf_info));
PRINT(("OpenSoundDeviceEngine::%s: %s: %s\n", if (ioctl(fFD, SNDCTL_DSP_GETOSPACE, &abinfo, sizeof(audio_buf_info)) < 0) {
__FUNCTION__, "SNDCTL_DSP_GET?SPACE", strerror(errno))); fprintf(stderr, "failed to retrieve driver buffer size!\n");
return -1; abinfo.bytes = 0;
} }
PRINT(("OSS: %cSPACE: { bytes=%d, fragments=%d, fragsize=%d, fragstotal=%d }\n", rec?'I':'O', abinfo.bytes, abinfo.fragments, abinfo.fragsize, abinfo.fragstotal)); fDriverBufferSize = abinfo.bytes;
// cache the first one in the Device
// so StartThread() knows the number of frags raw.buffer_size = fDriverBufferSize;
//if (!fFragments.fragstotal)
// memcpy(&fFragments, &abinfo, sizeof(abinfo));
// make sure buffer size is less than the driver's own buffer ( /2 to keep some margin )
// if (/*rec && raw.buffer_size &&*/ raw.buffer_size >= abinfo.fragsize * abinfo.fragstotal / 2)
// return B_MEDIA_BAD_FORMAT;
// if (!raw.buffer_size)
raw.buffer_size = abinfo.fragsize;// * abinfo.fragstotal / 4;//XXX
/* * (raw.format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
* raw.channel_count;*/
} else if (format.type == B_MEDIA_ENCODED_AUDIO) { } else if (format.type == B_MEDIA_ENCODED_AUDIO) {
media_raw_audio_format &raw = format.u.encoded_audio.output; media_raw_audio_format &raw = format.u.encoded_audio.output;
// XXX: do we really have to do this ? // XXX: do we really have to do this ?
@@ -680,6 +618,7 @@ status_t OpenSoundDeviceEngine::AcceptFormatFor(int fmt, media_format &format, b
} }
// cache it // cache it
fMediaFormat = format; fMediaFormat = format;
string_for_format(format, buf, 1024); string_for_format(format, buf, 1024);
PRINT(("%s: %s\n", __FUNCTION__, buf)); PRINT(("%s: %s\n", __FUNCTION__, buf));
return B_OK; return B_OK;
@@ -740,15 +679,16 @@ status_t OpenSoundDeviceEngine::SpecializeFormatFor(int fmt, media_format &forma
// endianness // endianness
if (!raw.byte_order) if (!raw.byte_order)
raw.byte_order = OpenSoundDevice::convert_oss_format_to_endian(afmt); raw.byte_order = OpenSoundDevice::convert_oss_format_to_endian(afmt);
if (raw.byte_order != OpenSoundDevice::convert_oss_format_to_endian(afmt)) { if ((int)raw.byte_order != OpenSoundDevice::convert_oss_format_to_endian(afmt)) {
Close(); Close();
return B_MEDIA_BAD_FORMAT; return B_MEDIA_BAD_FORMAT;
} }
// channel count // channel count
if (!raw.channel_count) if (raw.channel_count == 0)
raw.channel_count = (unsigned)Info()->min_channels; raw.channel_count = (unsigned)Info()->min_channels;
if (raw.channel_count < Info()->min_channels || raw.channel_count > Info()->max_channels) if ((int)raw.channel_count < Info()->min_channels
|| (int)raw.channel_count > Info()->max_channels)
return B_MEDIA_BAD_FORMAT; return B_MEDIA_BAD_FORMAT;
err = SetChannels(raw.channel_count); err = SetChannels(raw.channel_count);
if (err < B_OK) { if (err < B_OK) {
@@ -784,7 +724,7 @@ status_t OpenSoundDeviceEngine::SpecializeFormatFor(int fmt, media_format &forma
// memcpy(&fFragments, &abinfo, sizeof(abinfo)); // memcpy(&fFragments, &abinfo, sizeof(abinfo));
// make sure buffer size is less than the driver's own buffer ( /2 to keep some margin ) // make sure buffer size is less than the driver's own buffer ( /2 to keep some margin )
if (/*rec && raw.buffer_size &&*/ raw.buffer_size > abinfo.fragsize * abinfo.fragstotal / 4) if (/*rec && raw.buffer_size &&*/ (int)raw.buffer_size > abinfo.fragsize * abinfo.fragstotal / 4)
return B_MEDIA_BAD_FORMAT; return B_MEDIA_BAD_FORMAT;
if (!raw.buffer_size) if (!raw.buffer_size)
raw.buffer_size = abinfo.fragsize;//XXX raw.buffer_size = abinfo.fragsize;//XXX
@@ -57,19 +57,18 @@ virtual ssize_t Write(const void *buffer, size_t size);
size_t GetISpace(audio_buf_info *info=NULL); size_t GetISpace(audio_buf_info *info=NULL);
size_t GetOSpace(audio_buf_info *info=NULL); size_t GetOSpace(audio_buf_info *info=NULL);
int64 GetCurrentIPtr(int32 *fifoed=NULL, oss_count_t *info=NULL); int64 GetCurrentIPtr(int32* fifoed = NULL,
int64 GetCurrentOPtr(int32 *fifoed=NULL, oss_count_t *info=NULL); oss_count_t* info = NULL);
int64 GetCurrentOPtr(int32* fifoed = NULL,
size_t* fragmentPos = NULL);
int32 GetIOverruns(); int32 GetIOverruns();
int32 GetOUnderruns(); int32 GetOUnderruns();
int GetODelay(void); size_t DriverBufferSize() const;
status_t StartRecording(void); status_t StartRecording(void);
int64 PlayedFramesCount(void);
bigtime_t PlayedRealTime(void);
// suggest possibles // suggest possibles
status_t WildcardFormatFor(int fmt, media_format &format, bool rec=false); status_t WildcardFormatFor(int fmt, media_format &format, bool rec=false);
// suggest best // suggest best
@@ -90,6 +89,7 @@ friend class OpenSoundAddOn;
media_format fMediaFormat; media_format fMediaFormat;
int64 fPlayedFramesCount; int64 fPlayedFramesCount;
bigtime_t fPlayedRealTime; bigtime_t fPlayedRealTime;
size_t fDriverBufferSize;
}; };
#endif #endif
File diff suppressed because it is too large Load Diff
@@ -238,11 +238,10 @@ private:
status_t _StartRecThread(NodeOutput* output); status_t _StartRecThread(NodeOutput* output);
status_t _StopRecThread(NodeOutput* output); status_t _StopRecThread(NodeOutput* output);
void _AllocateBuffers(NodeOutput& channel);
BBuffer* _FillNextBuffer(audio_buf_info* abinfo, BBuffer* _FillNextBuffer(audio_buf_info* abinfo,
NodeOutput& channel); NodeOutput& channel);
void _UpdateTimeSource(audio_buf_info* abinfo, void _UpdateTimeSource(bigtime_t performanceTime,
NodeInput& input); bigtime_t realTime, float drift);
NodeOutput* _FindOutput( NodeOutput* _FindOutput(
@@ -271,9 +270,10 @@ private:
// and bit rate, not the defaults that are in the // and bit rate, not the defaults that are in the
// parameters // parameters
OpenSoundDevice* fDevice; OpenSoundDevice* fDevice;
bool fTimeSourceStarted; bool fTimeSourceStarted;
int64 fOldPlayedFramesCount; bigtime_t fTimeSourceStartTime;
bigtime_t fOldPlayedRealTime;
BParameterWeb* fWeb; BParameterWeb* fWeb;
BMessage fConfig; BMessage fConfig;
}; };
@@ -0,0 +1,30 @@
/*
* Copyright 2008 Stephan Aßmus, <superstippi@gmx.de>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#ifndef SUPPORT_FUNCTIONS_H
#define SUPPORT_FUNCTIONS_H
#include <MediaDefs.h>
static inline int32
bytes_per_frame(const media_format& format)
{
int32 channelCount = format.u.raw_audio.channel_count;
size_t sampleSize = format.u.raw_audio.format
& media_raw_audio_format::B_AUDIO_SIZE_MASK;
return sampleSize * channelCount;
}
static inline bigtime_t
time_for_buffer(size_t size, const media_format& format)
{
int32 frameSize = bytes_per_frame(format);
float frameRate = format.u.raw_audio.frame_rate;
return (bigtime_t)((double)size * 1000000 / frameSize / frameRate);
}
#endif // SUPPORT_FUNCTIONS_H