Commit f6163424 authored by David Reid's avatar David Reid

audio(4): Work on the new blocking read/write API.

parent 0683b399
...@@ -17390,24 +17390,25 @@ mal_result mal_device_init__audio4(mal_context* pContext, mal_device_type device ...@@ -17390,24 +17390,25 @@ mal_result mal_device_init__audio4(mal_context* pContext, mal_device_type device
} }
// What mini_al calls a fragment, audio4 calls a block. // What mini_al calls a fragment, audio4 calls a block.
mal_uint32 fragmentSizeInBytes = pDevice->bufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); mal_uint32 bufferSizeInBytes = pDevice->bufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
if (fragmentSizeInBytes < 16) { if (bufferSizeInBytes < 16) {
fragmentSizeInBytes = 16; bufferSizeInBytes = 16;
} }
AUDIO_INITINFO(&fdInfo); AUDIO_INITINFO(&fdInfo);
fdInfo.hiwat = mal_max(pDevice->periods, 5); fdInfo.hiwat = pDevice->periods;
fdInfo.lowat = (unsigned int)(fdInfo.hiwat * 0.75); fdInfo.lowat = pDevice->periods-1;
fdInfo.blocksize = fragmentSizeInBytes / fdInfo.hiwat; fdInfo.blocksize = bufferSizeInBytes / pDevice->periods;
if (ioctl(pDevice->audio4.fd, AUDIO_SETINFO, &fdInfo) < 0) { if (ioctl(pDevice->audio4.fd, AUDIO_SETINFO, &fdInfo) < 0) {
close(pDevice->audio4.fd); close(pDevice->audio4.fd);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
printf("blocksize=%d, hiwat=%d\n", fdInfo.blocksize, fdInfo.hiwat);
pDevice->periods = fdInfo.hiwat; pDevice->periods = fdInfo.hiwat;
pDevice->bufferSizeInFrames = (fdInfo.blocksize * fdInfo.hiwat) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); pDevice->bufferSizeInFrames = (fdInfo.blocksize * fdInfo.hiwat) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
pDevice->audio4.fragmentSizeInFrames = fdInfo.blocksize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
#else #else
// We need to retrieve the format of the device so we can know the channel count and sample rate. Then we // We need to retrieve the format of the device so we can know the channel count and sample rate. Then we
// can calculate the buffer size. // can calculate the buffer size.
...@@ -17455,27 +17456,16 @@ mal_result mal_device_init__audio4(mal_context* pContext, mal_device_type device ...@@ -17455,27 +17456,16 @@ mal_result mal_device_init__audio4(mal_context* pContext, mal_device_type device
} else { } else {
pDevice->internalChannels = fdPar.rchan; pDevice->internalChannels = fdPar.rchan;
} }
pDevice->internalSampleRate = fdPar.rate; pDevice->internalSampleRate = fdPar.rate;
pDevice->periods = fdPar.nblks; pDevice->periods = fdPar.nblks;
pDevice->bufferSizeInFrames = (fdPar.nblks * fdPar.round) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); pDevice->bufferSizeInFrames = (fdPar.nblks * fdPar.round) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
pDevice->audio4.fragmentSizeInFrames = fdPar.round / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
#endif #endif
// For the channel map, I'm not sure how to query the channel map (or if it's even possible). I'm just // For the channel map, I'm not sure how to query the channel map (or if it's even possible). I'm just
// using the channels defined in FreeBSD's sound(4) man page. // using the channels defined in FreeBSD's sound(4) man page.
mal_get_standard_channel_map(mal_standard_channel_map_sound4, pDevice->internalChannels, pDevice->internalChannelMap); mal_get_standard_channel_map(mal_standard_channel_map_sound4, pDevice->internalChannels, pDevice->internalChannelMap);
// When not using MMAP mode we need to use an intermediary buffer to the data transfer between the client
// and device. Everything is done by the size of a fragment.
pDevice->audio4.pIntermediaryBuffer = mal_malloc(pDevice->audio4.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
if (pDevice->audio4.pIntermediaryBuffer == NULL) {
close(pDevice->audio4.fd);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY);
}
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -17488,24 +17478,6 @@ mal_result mal_device_start__audio4(mal_device* pDevice) ...@@ -17488,24 +17478,6 @@ mal_result mal_device_start__audio4(mal_device* pDevice)
return MAL_INVALID_ARGS; return MAL_INVALID_ARGS;
} }
// The device is started by the next calls to read() and write(). For playback it's simple - just read
// data from the client, then write it to the device with write() which will in turn start the device.
// For capture it's a bit less intuitive - we do nothing (it'll be started automatically by the first
// call to read().
if (pDevice->type == mal_device_type_playback) {
// Playback. Need to load the entire buffer, which means we need to write a fragment for each period.
for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; iPeriod += 1) {
mal_device__read_frames_from_client(pDevice, pDevice->audio4.fragmentSizeInFrames, pDevice->audio4.pIntermediaryBuffer);
int bytesWritten = write(pDevice->audio4.fd, pDevice->audio4.pIntermediaryBuffer, pDevice->audio4.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
if (bytesWritten == -1) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
}
}
} else {
// Capture. Do nothing.
}
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -17530,117 +17502,21 @@ mal_result mal_device_stop__audio4(mal_device* pDevice) ...@@ -17530,117 +17502,21 @@ mal_result mal_device_stop__audio4(mal_device* pDevice)
return MAL_SUCCESS; return MAL_SUCCESS;
} }
mal_result mal_device_break_main_loop__audio4(mal_device* pDevice) mal_result mal_device_write__audio4(mal_device* pDevice, const void* pPCMFrames, mal_uint32 pcmFrameCount)
{ {
mal_assert(pDevice != NULL); int result = write(pDevice->audio4.fd, pPCMFrames, pcmFrameCount * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
if (result < 0) {
pDevice->audio4.breakFromMainLoop = MAL_TRUE; return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
return MAL_SUCCESS;
}
mal_result mal_device__wait__audio4(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
struct pollfd fds[1];
fds[0].fd = pDevice->audio4.fd;
fds[0].events = (pDevice->type == mal_device_type_playback) ? (POLLOUT | POLLWRBAND) : (POLLIN | POLLPRI);
int timeout = 2 * 1000;
int ioresult = poll(fds, mal_countof(fds), timeout);
if (ioresult < 0) {
#ifdef MAL_DEBUG_OUTPUT
printf("poll() failed: timeout=%d, ioresult=%d\n", pDevice->bufferSizeInMilliseconds, ioresult);
#endif
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to wait for device.", MAL_ERROR);
}
// Check for a timeout. This has been annoying in my testing. In my testing, when the device is unplugged it will just
// hang on the next calls to write(), ioctl(), etc. The only way I have figured out how to handle this is to wait for
// a timeout from poll(). In the unplugging case poll() will timeout, however there's no indication that the device is
// unusable - no flags are set, no errors are reported, nothing. To work around this I have decided to outright fail
// in the event of a timeout.
if (ioresult == 0) {
// Check for errors.
if ((fds[0].revents & (POLLERR | POLLNVAL)) != 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to wait for device.", MAL_NO_DEVICE);
}
if ((fds[0].revents & (POLLHUP)) != 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to wait for device. Disconnected.", MAL_NO_DEVICE);
}
// A return value of 0 from poll indicates a timeout.
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Timeout while waiting for device.", MAL_TIMEOUT);
} }
mal_assert(ioresult > 0);
return MAL_SUCCESS; return MAL_SUCCESS;
} }
mal_result mal_device_main_loop__audio4(mal_device* pDevice) mal_result mal_device_read__audio4(mal_device* pDevice, void* pPCMFrames, mal_uint32 pcmFrameCount)
{ {
mal_assert(pDevice != NULL); int result = read(pDevice->audio4.fd, pPCMFrames, pcmFrameCount * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels));
if (result < 0) {
pDevice->audio4.breakFromMainLoop = MAL_FALSE; return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE);
while (!pDevice->audio4.breakFromMainLoop) {
// Break from the main loop if the device isn't started anymore. Likely what's happened is the application
// has requested that the device be stopped.
if (!mal_device_is_started(pDevice)) {
break;
}
if (pDevice->type == mal_device_type_playback) {
// Playback.
mal_device__read_frames_from_client(pDevice, pDevice->audio4.fragmentSizeInFrames, pDevice->audio4.pIntermediaryBuffer);
// Wait for data to become available.
mal_result result = mal_device__wait__audio4(pDevice);
if (result != MAL_SUCCESS) {
return result;
}
size_t bytesToWrite = pDevice->audio4.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
while (bytesToWrite > 0) {
ssize_t bytesWritten = write(pDevice->audio4.fd, pDevice->audio4.pIntermediaryBuffer, bytesToWrite);
if (bytesWritten < 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
}
if (bytesWritten == 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to write any data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
}
bytesToWrite -= bytesWritten;
}
} else {
// Capture.
mal_result result = mal_device__wait__audio4(pDevice);
if (result != MAL_SUCCESS) {
return result;
}
size_t totalBytesRead = 0;
size_t bytesToRead = pDevice->audio4.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
while (bytesToRead > 0) {
ssize_t bytesRead = read(pDevice->audio4.fd, pDevice->audio4.pIntermediaryBuffer, bytesToRead);
if (bytesRead < 0) {
if (errno == EAGAIN) {
break;
}
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE);
}
if (bytesRead == 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audio4] Failed to read any data from the device.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE);
}
bytesToRead -= bytesRead;
totalBytesRead += bytesRead;
}
mal_uint32 framesRead = (mal_uint32)totalBytesRead / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
mal_device__send_frames_to_client(pDevice, framesRead, pDevice->audio4.pIntermediaryBuffer);
}
} }
return MAL_SUCCESS; return MAL_SUCCESS;
...@@ -17667,8 +17543,8 @@ mal_result mal_context_init__audio4(mal_context* pContext) ...@@ -17667,8 +17543,8 @@ mal_result mal_context_init__audio4(mal_context* pContext)
pContext->onDeviceUninit = mal_device_uninit__audio4; pContext->onDeviceUninit = mal_device_uninit__audio4;
pContext->onDeviceStart = mal_device_start__audio4; pContext->onDeviceStart = mal_device_start__audio4;
pContext->onDeviceStop = mal_device_stop__audio4; pContext->onDeviceStop = mal_device_stop__audio4;
pContext->onDeviceBreakMainLoop = mal_device_break_main_loop__audio4; pContext->onDeviceWrite = mal_device_write__audio4;
pContext->onDeviceMainLoop = mal_device_main_loop__audio4; pContext->onDeviceRead = mal_device_read__audio4;
return MAL_SUCCESS; return MAL_SUCCESS;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment