* lukaszmluki/master: lavd/pulse_audio_enc: respect minreq while checking buffer fullness lavd/pulse_audio_enc: signal that buffer is still writable after write lavd/pulse_audio_enc: add pointer checks lavd/pulse_audio_enc: add more buffer attributes lavd/fbdev_dec: implement fbdev_get_device_list callback lavd/fbdev_enc: move list device code to fbdev_common lavd/fbdev_enc: remove redundant assignments Merged-by: Michael Niedermayer <michaelni@gmx.at>tags/n2.3
| @@ -290,6 +290,20 @@ When both options are provided then the highest value is used | |||||
| are set to 0 (which is default), the device will use the default | are set to 0 (which is default), the device will use the default | ||||
| PulseAudio duration value. By default PulseAudio set buffer duration | PulseAudio duration value. By default PulseAudio set buffer duration | ||||
| to around 2 seconds. | to around 2 seconds. | ||||
| @item prebuf | |||||
| Specify pre-buffering size in bytes. The server does not start with | |||||
| playback before at least @option{prebuf} bytes are available in the | |||||
| buffer. By default this option is initialized to the same value as | |||||
| @option{buffer_size} or @option{buffer_duration} (whichever is bigger). | |||||
| @item minreq | |||||
| Specify minimum request size in bytes. The server does not request less | |||||
| than @option{minreq} bytes from the client, instead waits until the buffer | |||||
| is free enough to request more bytes at once. It is recommended to not set | |||||
| this option, which will initialize this to a value that is deemed sensible | |||||
| by the server. | |||||
| @end table | @end table | ||||
| @subsection Examples | @subsection Examples | ||||
| @@ -20,9 +20,13 @@ | |||||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
| */ | */ | ||||
| #include <unistd.h> | |||||
| #include <fcntl.h> | |||||
| #include <sys/ioctl.h> | |||||
| #include <stdlib.h> | #include <stdlib.h> | ||||
| #include "fbdev_common.h" | #include "fbdev_common.h" | ||||
| #include "libavutil/common.h" | #include "libavutil/common.h" | ||||
| #include "avdevice.h" | |||||
| struct rgb_pixfmt_map_entry { | struct rgb_pixfmt_map_entry { | ||||
| int bits_per_pixel; | int bits_per_pixel; | ||||
| @@ -65,3 +69,61 @@ const char* ff_fbdev_default_device() | |||||
| return dev; | return dev; | ||||
| } | } | ||||
| int ff_fbdev_get_device_list(AVDeviceInfoList *device_list) | |||||
| { | |||||
| struct fb_var_screeninfo varinfo; | |||||
| struct fb_fix_screeninfo fixinfo; | |||||
| char device_file[12]; | |||||
| AVDeviceInfo *device = NULL; | |||||
| int i, fd, ret = 0; | |||||
| const char *default_device = ff_fbdev_default_device(); | |||||
| if (!device_list) | |||||
| return AVERROR(EINVAL); | |||||
| for (i = 0; i <= 31; i++) { | |||||
| snprintf(device_file, sizeof(device_file), "/dev/fb%d", i); | |||||
| if ((fd = avpriv_open(device_file, O_RDWR)) < 0) | |||||
| continue; | |||||
| if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) == -1) | |||||
| goto fail_device; | |||||
| if (ioctl(fd, FBIOGET_FSCREENINFO, &fixinfo) == -1) | |||||
| goto fail_device; | |||||
| device = av_mallocz(sizeof(AVDeviceInfo)); | |||||
| if (!device) { | |||||
| ret = AVERROR(ENOMEM); | |||||
| goto fail_device; | |||||
| } | |||||
| device->device_name = av_strdup(device_file); | |||||
| device->device_description = av_strdup(fixinfo.id); | |||||
| if (!device->device_name || !device->device_description) { | |||||
| ret = AVERROR(ENOMEM); | |||||
| goto fail_device; | |||||
| } | |||||
| if ((ret = av_dynarray_add_nofree(&device_list->devices, | |||||
| &device_list->nb_devices, device)) < 0) | |||||
| goto fail_device; | |||||
| if (default_device && !strcmp(device->device_name, default_device)) { | |||||
| device_list->default_device = device_list->nb_devices - 1; | |||||
| default_device = NULL; | |||||
| } | |||||
| close(fd); | |||||
| continue; | |||||
| fail_device: | |||||
| if (device) { | |||||
| av_free(device->device_name); | |||||
| av_free(device->device_description); | |||||
| av_freep(&device); | |||||
| } | |||||
| if (fd >= 0) | |||||
| close(fd); | |||||
| if (ret < 0) | |||||
| return ret; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| @@ -27,8 +27,12 @@ | |||||
| #include <linux/fb.h> | #include <linux/fb.h> | ||||
| #include "libavutil/pixfmt.h" | #include "libavutil/pixfmt.h" | ||||
| struct AVDeviceInfoList; | |||||
| enum AVPixelFormat ff_get_pixfmt_from_fb_varinfo(struct fb_var_screeninfo *varinfo); | enum AVPixelFormat ff_get_pixfmt_from_fb_varinfo(struct fb_var_screeninfo *varinfo); | ||||
| const char* ff_fbdev_default_device(void); | const char* ff_fbdev_default_device(void); | ||||
| int ff_fbdev_get_device_list(struct AVDeviceInfoList *device_list); | |||||
| #endif /* AVDEVICE_FBDEV_COMMON_H */ | #endif /* AVDEVICE_FBDEV_COMMON_H */ | ||||
| @@ -205,6 +205,11 @@ static av_cold int fbdev_read_close(AVFormatContext *avctx) | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| static int fbdev_get_device_list(AVFormatContext *s, AVDeviceInfoList *device_list) | |||||
| { | |||||
| return ff_fbdev_get_device_list(device_list); | |||||
| } | |||||
| #define OFFSET(x) offsetof(FBDevContext, x) | #define OFFSET(x) offsetof(FBDevContext, x) | ||||
| #define DEC AV_OPT_FLAG_DECODING_PARAM | #define DEC AV_OPT_FLAG_DECODING_PARAM | ||||
| static const AVOption options[] = { | static const AVOption options[] = { | ||||
| @@ -227,6 +232,7 @@ AVInputFormat ff_fbdev_demuxer = { | |||||
| .read_header = fbdev_read_header, | .read_header = fbdev_read_header, | ||||
| .read_packet = fbdev_read_packet, | .read_packet = fbdev_read_packet, | ||||
| .read_close = fbdev_read_close, | .read_close = fbdev_read_close, | ||||
| .get_device_list = fbdev_get_device_list, | |||||
| .flags = AVFMT_NOFILE, | .flags = AVFMT_NOFILE, | ||||
| .priv_class = &fbdev_class, | .priv_class = &fbdev_class, | ||||
| }; | }; | ||||
| @@ -186,64 +186,7 @@ static av_cold int fbdev_write_trailer(AVFormatContext *h) | |||||
| static int fbdev_get_device_list(AVFormatContext *s, AVDeviceInfoList *device_list) | static int fbdev_get_device_list(AVFormatContext *s, AVDeviceInfoList *device_list) | ||||
| { | { | ||||
| struct fb_var_screeninfo varinfo; | |||||
| struct fb_fix_screeninfo fixinfo; | |||||
| char device_file[12]; | |||||
| AVDeviceInfo *device = NULL; | |||||
| int i, fd = -1, ret = 0; | |||||
| const char *default_device = ff_fbdev_default_device(); | |||||
| if (!device_list) | |||||
| return AVERROR(EINVAL); | |||||
| for (i = 0; i <= 31; i++) { | |||||
| snprintf(device_file, sizeof(device_file), "/dev/fb%d", i); | |||||
| if ((fd = avpriv_open(device_file, O_RDWR)) < 0) | |||||
| continue; | |||||
| if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) == -1) | |||||
| goto fail_device; | |||||
| if (ioctl(fd, FBIOGET_FSCREENINFO, &fixinfo) == -1) | |||||
| goto fail_device; | |||||
| device = av_mallocz(sizeof(AVDeviceInfo)); | |||||
| if (!device) { | |||||
| ret = AVERROR(ENOMEM); | |||||
| goto fail_device; | |||||
| } | |||||
| device->device_name = av_strdup(device_file); | |||||
| device->device_description = av_strdup(fixinfo.id); | |||||
| if (!device->device_name || !device->device_description) { | |||||
| ret = AVERROR(ENOMEM); | |||||
| goto fail_device; | |||||
| } | |||||
| if ((ret = av_dynarray_add_nofree(&device_list->devices, | |||||
| &device_list->nb_devices, device)) < 0) | |||||
| goto fail_device; | |||||
| if (default_device && !strcmp(device->device_name, default_device)) { | |||||
| device_list->default_device = device_list->nb_devices - 1; | |||||
| default_device = NULL; | |||||
| } | |||||
| close(fd); | |||||
| fd = -1; | |||||
| continue; | |||||
| fail_device: | |||||
| if (device) { | |||||
| av_free(device->device_name); | |||||
| av_free(device->device_description); | |||||
| av_freep(&device); | |||||
| } | |||||
| if (fd >= 0) { | |||||
| close(fd); | |||||
| fd = -1; | |||||
| } | |||||
| if (ret < 0) | |||||
| return ret; | |||||
| } | |||||
| return 0; | |||||
| return ff_fbdev_get_device_list(device_list); | |||||
| } | } | ||||
| #define OFFSET(x) offsetof(FBDevContext, x) | #define OFFSET(x) offsetof(FBDevContext, x) | ||||
| @@ -38,6 +38,8 @@ typedef struct PulseData { | |||||
| int64_t timestamp; | int64_t timestamp; | ||||
| int buffer_size; /**< Buffer size in bytes */ | int buffer_size; /**< Buffer size in bytes */ | ||||
| int buffer_duration; /**< Buffer size in ms, recalculated to buffer_size */ | int buffer_duration; /**< Buffer size in ms, recalculated to buffer_size */ | ||||
| int prebuf; | |||||
| int minreq; | |||||
| int last_result; | int last_result; | ||||
| pa_threaded_mainloop *mainloop; | pa_threaded_mainloop *mainloop; | ||||
| pa_context *ctx; | pa_context *ctx; | ||||
| @@ -475,6 +477,10 @@ static av_cold int pulse_write_header(AVFormatContext *h) | |||||
| av_log(s, AV_LOG_DEBUG, "Real buffer length is %u bytes\n", buffer_attributes.tlength); | av_log(s, AV_LOG_DEBUG, "Real buffer length is %u bytes\n", buffer_attributes.tlength); | ||||
| } else if (s->buffer_size) | } else if (s->buffer_size) | ||||
| buffer_attributes.tlength = s->buffer_size; | buffer_attributes.tlength = s->buffer_size; | ||||
| if (s->prebuf) | |||||
| buffer_attributes.prebuf = s->prebuf; | |||||
| if (s->minreq) | |||||
| buffer_attributes.minreq = s->minreq; | |||||
| sample_spec.format = ff_codec_id_to_pulse_format(st->codec->codec_id); | sample_spec.format = ff_codec_id_to_pulse_format(st->codec->codec_id); | ||||
| sample_spec.rate = st->codec->sample_rate; | sample_spec.rate = st->codec->sample_rate; | ||||
| @@ -578,6 +584,14 @@ static av_cold int pulse_write_header(AVFormatContext *h) | |||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| /* read back buffer attributes for future use */ | |||||
| buffer_attributes = *pa_stream_get_buffer_attr(s->stream); | |||||
| s->buffer_size = buffer_attributes.tlength; | |||||
| s->prebuf = buffer_attributes.prebuf; | |||||
| s->minreq = buffer_attributes.minreq; | |||||
| av_log(s, AV_LOG_DEBUG, "Real buffer attributes: size: %d, prebuf: %d, minreq: %d\n", | |||||
| s->buffer_size, s->prebuf, s->minreq); | |||||
| pa_threaded_mainloop_unlock(s->mainloop); | pa_threaded_mainloop_unlock(s->mainloop); | ||||
| if ((ret = pulse_subscribe_events(s)) < 0) { | if ((ret = pulse_subscribe_events(s)) < 0) { | ||||
| @@ -610,6 +624,7 @@ static int pulse_write_packet(AVFormatContext *h, AVPacket *pkt) | |||||
| { | { | ||||
| PulseData *s = h->priv_data; | PulseData *s = h->priv_data; | ||||
| int ret; | int ret; | ||||
| int64_t writable_size; | |||||
| if (!pkt) | if (!pkt) | ||||
| return pulse_flash_stream(s); | return pulse_flash_stream(s); | ||||
| @@ -632,7 +647,7 @@ static int pulse_write_packet(AVFormatContext *h, AVPacket *pkt) | |||||
| av_log(s, AV_LOG_ERROR, "PulseAudio stream is in invalid state.\n"); | av_log(s, AV_LOG_ERROR, "PulseAudio stream is in invalid state.\n"); | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| while (!pa_stream_writable_size(s->stream)) { | |||||
| while (pa_stream_writable_size(s->stream) < s->minreq) { | |||||
| if (s->nonblocking) { | if (s->nonblocking) { | ||||
| pa_threaded_mainloop_unlock(s->mainloop); | pa_threaded_mainloop_unlock(s->mainloop); | ||||
| return AVERROR(EAGAIN); | return AVERROR(EAGAIN); | ||||
| @@ -644,6 +659,9 @@ static int pulse_write_packet(AVFormatContext *h, AVPacket *pkt) | |||||
| av_log(s, AV_LOG_ERROR, "pa_stream_write failed: %s\n", pa_strerror(ret)); | av_log(s, AV_LOG_ERROR, "pa_stream_write failed: %s\n", pa_strerror(ret)); | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| if ((writable_size = pa_stream_writable_size(s->stream)) >= s->minreq) | |||||
| avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_BUFFER_WRITABLE, &writable_size, sizeof(writable_size)); | |||||
| pa_threaded_mainloop_unlock(s->mainloop); | pa_threaded_mainloop_unlock(s->mainloop); | ||||
| return 0; | return 0; | ||||
| @@ -678,8 +696,10 @@ static void pulse_get_output_timestamp(AVFormatContext *h, int stream, int64_t * | |||||
| pa_threaded_mainloop_lock(s->mainloop); | pa_threaded_mainloop_lock(s->mainloop); | ||||
| pa_stream_get_latency(s->stream, &latency, &neg); | pa_stream_get_latency(s->stream, &latency, &neg); | ||||
| pa_threaded_mainloop_unlock(s->mainloop); | pa_threaded_mainloop_unlock(s->mainloop); | ||||
| *wall = av_gettime(); | |||||
| *dts = s->timestamp - (neg ? -latency : latency); | |||||
| if (wall) | |||||
| *wall = av_gettime(); | |||||
| if (dts) | |||||
| *dts = s->timestamp - (neg ? -latency : latency); | |||||
| } | } | ||||
| static int pulse_get_device_list(AVFormatContext *h, AVDeviceInfoList *device_list) | static int pulse_get_device_list(AVFormatContext *h, AVDeviceInfoList *device_list) | ||||
| @@ -745,6 +765,8 @@ static const AVOption options[] = { | |||||
| { "device", "set device name", OFFSET(device), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, | { "device", "set device name", OFFSET(device), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, | ||||
| { "buffer_size", "set buffer size in bytes", OFFSET(buffer_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | { "buffer_size", "set buffer size in bytes", OFFSET(buffer_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | ||||
| { "buffer_duration", "set buffer duration in millisecs", OFFSET(buffer_duration), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | { "buffer_duration", "set buffer duration in millisecs", OFFSET(buffer_duration), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | ||||
| { "prebuf", "set pre-buffering size", OFFSET(prebuf), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | |||||
| { "minreq", "set minimum request size", OFFSET(minreq), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, | |||||
| { NULL } | { NULL } | ||||
| }; | }; | ||||