diff --git a/Makefile b/Makefile index 670eb8de26..d39f37c971 100644 --- a/Makefile +++ b/Makefile @@ -84,8 +84,7 @@ SOURCES-$(ALSA) += audio/out/ao_alsa.c SOURCES-$(CACA) += video/out/vo_caca.c SOURCES-$(SDL) += audio/out/ao_sdl.c SOURCES-$(SDL2) += video/out/vo_sdl.c -SOURCES-$(COREAUDIO) += audio/out/ao_coreaudio.c \ - audio/out/ao_coreaudio/ca_ringbuffer.c +SOURCES-$(COREAUDIO) += audio/out/ao_coreaudio.c SOURCES-$(COREVIDEO) += video/out/vo_corevideo.m SOURCES-$(DIRECT3D) += video/out/vo_direct3d.c \ video/out/w32_common.c @@ -180,6 +179,7 @@ SOURCES = talloc.c \ core/mp_common.c \ core/mp_fifo.c \ core/mp_msg.c \ + core/mp_ring.c \ core/mplayer.c \ core/options.c \ core/parser-cfg.c \ diff --git a/audio/format.c b/audio/format.c index 5b1262956c..f9bfcb5ba8 100644 --- a/audio/format.c +++ b/audio/format.c @@ -110,6 +110,16 @@ static bool af_fmt_valid(int format) return (format & AF_FORMAT_MASK) == format; } +int af_fmt_seconds_to_bytes(int format, float seconds, int channels) +{ + int bps = (af_fmt2bits(format) / 8); + int framelen = channels * bps; + int bytes = seconds * bps; + if (bytes % framelen) + bytes += framelen - (bytes % framelen); + return bytes; +} + int af_str2fmt_short(bstr str) { if (bstr_startswith0(str, "0x")) { diff --git a/audio/format.h b/audio/format.h index 30a4aa1cea..bbc7f0fa69 100644 --- a/audio/format.h +++ b/audio/format.h @@ -132,6 +132,7 @@ extern const struct af_fmt_entry af_fmtstr_table[]; int af_str2fmt_short(bstr str); int af_fmt2bits(int format); char* af_fmt2str(int format, char* str, int size); +int af_fmt_seconds_to_bytes(int format, float seconds, int channels); const char* af_fmt2str_short(int format); #endif /* MPLAYER_AF_FORMAT_H */ diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c index 930bb2d741..d3491f17f8 100644 --- a/audio/out/ao_coreaudio.c +++ b/audio/out/ao_coreaudio.c @@ -49,8 +49,7 @@ #include "audio/format.h" #include "osdep/timer.h" #include "core/subopt-helper.h" - -#include "ao_coreaudio/ca_ringbuffer_internal.h" +#include "core/mp_ring.h" #define ca_msg(a, b, c ...) mp_msg(a, b, "AO: [coreaudio] " c) @@ -58,10 +57,10 @@ static void audio_pause(struct ao *ao); static void audio_resume(struct ao *ao); static void reset(struct ao *ao); -static void print_buffer(struct ca_ringbuffer *buffer) +static void print_buffer(struct mp_ring *buffer) { void *tctx = talloc_new(NULL); - ca_msg(MSGT_AO, MSGL_V, "%s\n", ca_ringbuffer_repr(buffer, tctx)); + ca_msg(MSGT_AO, MSGL_V, "%s\n", mp_ring_repr(buffer, tctx)); talloc_free(tctx); } @@ -92,7 +91,7 @@ struct priv int packetSize; int paused; - struct ca_ringbuffer *buffer; + struct mp_ring *buffer; }; static OSStatus theRenderProc(void *inRefCon, @@ -104,14 +103,14 @@ static OSStatus theRenderProc(void *inRefCon, struct ao *ao = inRefCon; struct priv *p = ao->priv; - int buffered = ca_ringbuffer_buffered(p->buffer); + int buffered = mp_ring_buffered(p->buffer); int requested = inNumFrames * p->packetSize; if (buffered > requested) buffered = requested; if (buffered) { - ca_ringbuffer_read(p->buffer, + mp_ring_read(p->buffer, (unsigned char *)ioData->mBuffers[0].mData, buffered); } else { @@ -622,8 +621,9 @@ static int init(struct ao *ao, char *params) ao->bps = ao->samplerate * inDesc.mBytesPerFrame; ao->buffersize = ao->bps; - p->buffer = ca_ringbuffer_new2(p, ao->bps, maxFrames); - ao->outburst = ca_ringbuffer_chunk_size(p->buffer); + int bufbytes = af_fmt_seconds_to_bytes(ao->format, 0.5, ao->channels.num); + p->buffer = mp_ring_new(p, bufbytes); + ao->outburst = maxFrames; print_buffer(p->buffer); @@ -859,8 +859,9 @@ static int OpenSPDIF(struct ao *ao) /* For ac3/dts, just use packet size 6144 bytes as chunk size. */ int chunk_size = p->stream_format.mBytesPerPacket; ao->buffersize = ao->bps; - p->buffer = ca_ringbuffer_new2(p, ao->bps, chunk_size); - ao->outburst = ca_ringbuffer_chunk_size(p->buffer); + int bufbytes = af_fmt_seconds_to_bytes(ao->format, 0.5, ao->channels.num); + p->buffer = mp_ring_new(p, bufbytes); + ao->outburst = chunk_size; print_buffer(p->buffer); @@ -1071,7 +1072,7 @@ static OSStatus RenderCallbackSPDIF(AudioDeviceID inDevice, { struct ao *ao = threadGlobals; struct priv *p = ao->priv; - int amt = ca_ringbuffer_buffered(p->buffer); + int amt = mp_ring_buffered(p->buffer); AudioBuffer ca_buffer = outOutputData->mBuffers[p->i_stream_index]; int req = ca_buffer.mDataByteSize; @@ -1079,9 +1080,9 @@ static OSStatus RenderCallbackSPDIF(AudioDeviceID inDevice, amt = req; if (amt) { if (p->b_muted) { - ca_ringbuffer_read(p->buffer, NULL, amt); + mp_ring_read(p->buffer, NULL, amt); } else { - ca_ringbuffer_read(p->buffer, (unsigned char *)ca_buffer.mData, amt); + mp_ring_read(p->buffer, (unsigned char *)ca_buffer.mData, amt); } } @@ -1116,7 +1117,7 @@ static int play(struct ao *ao, void *output_samples, int num_bytes, int flags) "Detected current stream does not support digital.\n"); } - wrote = ca_ringbuffer_write(p->buffer, output_samples, num_bytes); + wrote = mp_ring_write(p->buffer, output_samples, num_bytes); audio_resume(ao); return wrote; @@ -1127,7 +1128,7 @@ static void reset(struct ao *ao) { struct priv *p = ao->priv; audio_pause(ao); - ca_ringbuffer_reset(p->buffer); + mp_ring_reset(p->buffer); } @@ -1135,7 +1136,7 @@ static void reset(struct ao *ao) static int get_space(struct ao *ao) { struct priv *p = ao->priv; - return ca_ringbuffer_available(p->buffer); + return mp_ring_available(p->buffer); } @@ -1144,7 +1145,7 @@ static float get_delay(struct ao *ao) { // inaccurate, should also contain the data buffered e.g. by the OS struct priv *p = ao->priv; - return ca_ringbuffer_buffered(p->buffer) / (float)ao->bps; + return mp_ring_buffered(p->buffer) / (float)ao->bps; } static void uninit(struct ao *ao, bool immed) @@ -1154,9 +1155,9 @@ static void uninit(struct ao *ao, bool immed) if (!immed) { long long timeleft = - (1000000LL * ca_ringbuffer_buffered(p->buffer)) / ao->bps; - ca_msg(MSGT_AO, MSGL_DBG2, "%d bytes left @%d bps (%d usec)\n", ca_ringbuffer_buffered( - p->buffer), ao->bps, (int)timeleft); + (1000000LL * mp_ring_buffered(p->buffer)) / ao->bps; + ca_msg(MSGT_AO, MSGL_DBG2, "%d bytes left @%d bps (%d usec)\n", + mp_ring_buffered(p->buffer), ao->bps, (int)timeleft); mp_sleep_us((int)timeleft); } diff --git a/audio/out/ao_coreaudio/ca_ringbuffer.c b/audio/out/ao_coreaudio/ca_ringbuffer.c deleted file mode 100644 index 717466945d..0000000000 --- a/audio/out/ao_coreaudio/ca_ringbuffer.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . - */ - -#include -#include "talloc.h" - -#include "ca_ringbuffer_internal.h" - -struct ca_ringbuffer { - AVFifoBuffer *fifo; - int len; - int chunks; - int chunk_size; -}; - -struct ca_ringbuffer *ca_ringbuffer_new(void *talloc_ctx, int chunks, int chunk_size) -{ - struct ca_ringbuffer *buffer = - talloc_zero(talloc_ctx, struct ca_ringbuffer); - - *buffer = (struct ca_ringbuffer) { - .fifo = av_fifo_alloc(chunks * chunk_size), - .len = chunks * chunk_size, - .chunks = chunks, - .chunk_size = chunk_size, - }; - - return buffer; -} - -struct ca_ringbuffer *ca_ringbuffer_new2(void *talloc_ctx, int bps, int chunk_size) -{ - int chunks = (bps + chunk_size - 1) / chunk_size; - return ca_ringbuffer_new(talloc_ctx, chunks, chunk_size); -} - -int ca_ringbuffer_read(struct ca_ringbuffer *buffer, - unsigned char *data, int len) -{ - int buffered = ca_ringbuffer_buffered(buffer); - if (len > buffered) - len = buffered; - if (data) - av_fifo_generic_read(buffer->fifo, data, len, NULL); - else - av_fifo_drain(buffer->fifo, len); - return len; -} - -int ca_ringbuffer_write(struct ca_ringbuffer *buffer, - unsigned char *data, int len) -{ - int free = buffer->len - av_fifo_size(buffer->fifo); - if (len > free) - len = free; - return av_fifo_generic_write(buffer->fifo, data, len, NULL); -} - -void ca_ringbuffer_reset(struct ca_ringbuffer *buffer) -{ - av_fifo_reset(buffer->fifo); -} - -int ca_ringbuffer_available(struct ca_ringbuffer *buffer) -{ - return ca_ringbuffer_size(buffer) - ca_ringbuffer_buffered(buffer); -} - -int ca_ringbuffer_size(struct ca_ringbuffer *buffer) -{ - return buffer->len; -} - -int ca_ringbuffer_buffered(struct ca_ringbuffer *buffer) -{ - return av_fifo_size(buffer->fifo); -} - -int ca_ringbuffer_chunk_size(struct ca_ringbuffer *buffer) -{ - return buffer->chunk_size; -} - -char *ca_ringbuffer_repr(struct ca_ringbuffer *buffer, void *talloc_ctx) -{ - return talloc_asprintf( - talloc_ctx, - "Ringbuffer { .chunks = %d bytes, .chunk_size = %d bytes, .size = %d bytes }", - buffer->chunks, - ca_ringbuffer_chunk_size(buffer), - ca_ringbuffer_size(buffer)); -} diff --git a/audio/out/ao_coreaudio/ca_ringbuffer_internal.h b/audio/out/ao_coreaudio/ca_ringbuffer_internal.h deleted file mode 100644 index 50b41871b9..0000000000 --- a/audio/out/ao_coreaudio/ca_ringbuffer_internal.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . - */ - -#ifndef MPV_AUDIO_OUT_CA_RINGBUFFER_H -#define MPV_AUDIO_OUT_CA_RINGBUFFER_H - -struct ca_ringbuffer; - -struct ca_ringbuffer *ca_ringbuffer_new(void *talloc_ctx, int chunks, int chunk_size); -struct ca_ringbuffer *ca_ringbuffer_new2(void *talloc_ctx, int bps, int chunk_size); -int ca_ringbuffer_read(struct ca_ringbuffer *buffer, unsigned char *data, int len); -int ca_ringbuffer_write(struct ca_ringbuffer *buffer, unsigned char *data, int len); - -void ca_ringbuffer_reset(struct ca_ringbuffer *buffer); - -int ca_ringbuffer_available(struct ca_ringbuffer *buffer); -int ca_ringbuffer_size(struct ca_ringbuffer *buffer); -int ca_ringbuffer_buffered(struct ca_ringbuffer *buffer); -int ca_ringbuffer_chunk_size(struct ca_ringbuffer *buffer); - -char *ca_ringbuffer_repr(struct ca_ringbuffer *buffer, void *talloc_ctx); - -#endif diff --git a/core/mp_memory_barrier.h b/core/mp_memory_barrier.h new file mode 100644 index 0000000000..e27825de8f --- /dev/null +++ b/core/mp_memory_barrier.h @@ -0,0 +1,23 @@ +/* + * This file is part of mpv. + * Copyright (c) 2013 Stefano Pigozzi + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +// At this point both gcc and clang had __sync_synchronize support for some +// time. We only support a full memory barrier. + +#define mp_memory_barrier() __sync_synchronize() +#define mp_atomic_add_and_fetch(a, b) __sync_add_and_fetch(a, b) diff --git a/core/mp_ring.c b/core/mp_ring.c new file mode 100644 index 0000000000..207dc62e86 --- /dev/null +++ b/core/mp_ring.c @@ -0,0 +1,155 @@ +/* + * This file is part of mpv. + * Copyright (c) 2012 wm4 + * Copyright (c) 2013 Stefano Pigozzi + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include +#include +#include +#include "talloc.h" +#include "core/mp_memory_barrier.h" +#include "core/mp_ring.h" + +struct mp_ring { + uint8_t *buffer; + + /* Positions of thes first readable/writeable chunks. Do not read this + * fields but use the atomic private accessors `mp_ring_get_wpos` + * and `mp_ring_get_rpos`. */ + uint32_t rpos, wpos; +}; + +static uint32_t mp_ring_get_wpos(struct mp_ring *buffer) +{ + mp_memory_barrier(); + return buffer->wpos; +} + +static uint32_t mp_ring_get_rpos(struct mp_ring *buffer) +{ + mp_memory_barrier(); + return buffer->rpos; +} + +struct mp_ring *mp_ring_new(void *talloc_ctx, int size) +{ + struct mp_ring *ringbuffer = + talloc_zero(talloc_ctx, struct mp_ring); + + *ringbuffer = (struct mp_ring) { + .buffer = talloc_size(talloc_ctx, size), + }; + + return ringbuffer; +} + +int mp_ring_drain(struct mp_ring *buffer, int len) +{ + int buffered = mp_ring_buffered(buffer); + int drain_len = FFMIN(len, buffered); + mp_atomic_add_and_fetch(&buffer->rpos, drain_len); + mp_memory_barrier(); + return drain_len; +} + +int mp_ring_read(struct mp_ring *buffer, unsigned char *dest, int len) +{ + if (!dest) return mp_ring_drain(buffer, len); + + int size = mp_ring_size(buffer); + int buffered = mp_ring_buffered(buffer); + int read_len = FFMIN(len, buffered); + int read_ptr = mp_ring_get_rpos(buffer) % size; + + int len1 = FFMIN(size - read_ptr, read_len); + int len2 = read_len - len1; + + memcpy(dest, buffer->buffer + read_ptr, len1); + memcpy(dest + len1, buffer->buffer, len2); + + mp_atomic_add_and_fetch(&buffer->rpos, read_len); + mp_memory_barrier(); + + return read_len; +} + +int mp_ring_read_cb(struct mp_ring *buffer, void *ctx, int len, + void (*func)(void*, void*, int)) +{ + // The point of this function is defining custom read behaviour, assume + // it's a programmers error if func is null. + assert(func); + + int size = mp_ring_size(buffer); + int buffered = mp_ring_buffered(buffer); + int read_len = FFMIN(len, buffered); + int read_ptr = mp_ring_get_rpos(buffer) % size; + + func(ctx, buffer->buffer + read_ptr, len); + + return mp_ring_drain(buffer, read_len); +} + +int mp_ring_write(struct mp_ring *buffer, unsigned char *src, int len) +{ + int size = mp_ring_size(buffer); + int free = mp_ring_available(buffer); + int write_len = FFMIN(len, free); + int write_ptr = mp_ring_get_wpos(buffer) % size; + + int len1 = FFMIN(size - write_ptr, write_len); + int len2 = write_len - len1; + + memcpy(buffer->buffer + write_ptr, src, len1); + memcpy(buffer->buffer, src + len1, len2); + + mp_atomic_add_and_fetch(&buffer->wpos, write_len); + mp_memory_barrier(); + + return write_len; +} + +void mp_ring_reset(struct mp_ring *buffer) +{ + buffer->wpos = buffer->rpos = 0; + mp_memory_barrier(); +} + +int mp_ring_available(struct mp_ring *buffer) +{ + return mp_ring_size(buffer) - mp_ring_buffered(buffer); +} + +int mp_ring_size(struct mp_ring *buffer) +{ + return talloc_get_size(buffer->buffer); +} + +int mp_ring_buffered(struct mp_ring *buffer) +{ + return (mp_ring_get_wpos(buffer) - mp_ring_get_rpos(buffer)); +} + +char *mp_ring_repr(struct mp_ring *buffer, void *talloc_ctx) +{ + return talloc_asprintf( + talloc_ctx, + "Ringbuffer { .size = %dB, .buffered = %dB, .available = %dB }", + mp_ring_size(buffer), + mp_ring_buffered(buffer), + mp_ring_available(buffer)); +} diff --git a/core/mp_ring.h b/core/mp_ring.h new file mode 100644 index 0000000000..52e885287d --- /dev/null +++ b/core/mp_ring.h @@ -0,0 +1,125 @@ +/* + * This file is part of mpv. + * Copyright (c) 2012 wm4 + * Copyright (c) 2013 Stefano Pigozzi + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#ifndef MPV_MP_RING_H +#define MPV_MP_RING_H + +/** + * A simple non-blocking SPSC (single producer, single consumer) ringbuffer + * implementation. Thread safety is accomplished through atomic operations. + */ + +struct mp_ring; + +/** + * Instantiate a new ringbuffer + * + * talloc_ctx: talloc context of the newly created object + * size: total size in bytes + * return: the newly created ringbuffer + */ +struct mp_ring *mp_ring_new(void *talloc_ctx, int size); + +/** + * Read data from the ringbuffer + * + * buffer: target ringbuffer instance + * dest: destination buffer for the read data. If NULL read data is discarded. + * len: maximum number of bytes to read + * return: number of bytes read + */ +int mp_ring_read(struct mp_ring *buffer, unsigned char *dest, int len); + +/** + * Read data from the ringbuffer + * + * This function behaves similarly to `av_fifo_generic_read` and was actually + * added for compatibility with code that was written for it. + * This function will drain the returned amount of bytes from the ringbuffer + * so you don't have to handle that in inside `func`. + * + * buffer: target ringbuffer instance + * ctx: context for the callback function + * len: maximum number of bytes to read + * func: callback function to customize reading behaviour + * return: number of bytes read + */ +int mp_ring_read_cb(struct mp_ring *buffer, void *ctx, int len, + void (*func)(void*, void*, int)); + +/** + * Write data to the ringbuffer + * + * buffer: target ringbuffer instance + * src: source buffer for the write data + * len: maximum number of bytes to write + * return: number of bytes written + */ +int mp_ring_write(struct mp_ring *buffer, unsigned char *src, int len); + +/** + * Drain data from the ringbuffer + * + * buffer: target ringbuffer instance + * len: maximum number of bytes to drain + * return: number of bytes drained + */ +int mp_ring_drain(struct mp_ring *buffer, int len); + +/** + * Reset the ringbuffer discarding any content + * + * buffer: target ringbuffer instance + */ +void mp_ring_reset(struct mp_ring *buffer); + +/** + * Get the available size for writing + * + * buffer: target ringbuffer instance + * return: number of bytes that can be written + */ +int mp_ring_available(struct mp_ring *buffer); + +/** + * Get the total size + * + * buffer: target ringbuffer instance + * return: total ringbuffer size + */ +int mp_ring_size(struct mp_ring *buffer); + +/** + * Get the available size for reading + * + * buffer: target ringbuffer instance + * return: number of bytes ready for reading + */ +int mp_ring_buffered(struct mp_ring *buffer); + +/** + * Get a string representation of the ringbuffer + * + * buffer: target ringbuffer instance + * talloc_ctx: talloc context of the newly created string + * return: string representing the ringbuffer + */ +char *mp_ring_repr(struct mp_ring *buffer, void *talloc_ctx); + +#endif