diff mbox series

[FFmpeg-devel] avdevice: Add support for Solaris/NetBSD audio - sunau

Message ID 20200316012547.GA19513@homeworld.netbsd.org
State New
Headers show
Series [FFmpeg-devel] avdevice: Add support for Solaris/NetBSD audio - sunau
Related show

Checks

Context Check Description
andriy/ffmpeg-patchwork pending
andriy/ffmpeg-patchwork success Applied patch
andriy/ffmpeg-patchwork success Configure finished
andriy/ffmpeg-patchwork success Make finished
andriy/ffmpeg-patchwork success Make fate finished

Commit Message

nia March 16, 2020, 1:25 a.m. UTC
It's expected this will primarily be used on NetBSD as most
Solarish platforms seem to prefer either OSS or PulseAudio now.

The OSS support in FFmpeg has some problems on NetBSD due to
implementation differences between OSSv4 and NetBSD's OSS
emulation layer.

Using the relatively well documented native API, cross-referencing
with illumos documentation to maintain compatibility, we can get
much better results with higher quality recording and playback
without the problems caused by non-blocking I/O and reading from
the device with the incorrect number of channels.

Signed-off-by: Nia Alarie <nia@NetBSD.org>
---
 configure                |   4 ++
 doc/general.texi         |   1 +
 doc/indevs.texi          |  29 ++++++++
 doc/outdevs.texi         |  11 ++++
 libavdevice/Makefile     |   2 +
 libavdevice/alldevices.c |   2 +
 libavdevice/sunau.c      | 102 +++++++++++++++++++++++++++++
 libavdevice/sunau.h      |  48 ++++++++++++++
 libavdevice/sunau_dec.c  | 138 +++++++++++++++++++++++++++++++++++++++
 libavdevice/sunau_enc.c  | 114 ++++++++++++++++++++++++++++++++
 10 files changed, 451 insertions(+)
 create mode 100644 libavdevice/sunau.c
 create mode 100644 libavdevice/sunau.h
 create mode 100644 libavdevice/sunau_dec.c
 create mode 100644 libavdevice/sunau_enc.c
diff mbox series

Patch

diff --git a/configure b/configure
index 6ceb0c7af4..29a2b38629 100755
--- a/configure
+++ b/configure
@@ -2129,6 +2129,7 @@  HEADERS_LIST="
     sys_resource_h
     sys_select_h
     sys_soundcard_h
+    sys_audioio_h
     sys_time_h
     sys_un_h
     sys_videoio_h
@@ -3375,6 +3376,8 @@  opengl_outdev_deps="opengl"
 opengl_outdev_suggest="sdl2"
 oss_indev_deps_any="sys_soundcard_h"
 oss_outdev_deps_any="sys_soundcard_h"
+sunau_indev_deps_any="sys_audioio_h"
+sunau_outdev_deps_any="sys_audioio_h"
 pulse_indev_deps="libpulse"
 pulse_outdev_deps="libpulse"
 sdl2_outdev_deps="sdl2"
@@ -6082,6 +6085,7 @@  check_headers libcrystalhd/libcrystalhd_if.h
 check_headers malloc.h
 check_headers net/udplite.h
 check_headers poll.h
+check_headers sys/audioio.h
 check_headers sys/param.h
 check_headers sys/resource.h
 check_headers sys/select.h
diff --git a/doc/general.texi b/doc/general.texi
index 3684847ac1..2231a1442f 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -1382,6 +1382,7 @@  performance on systems without hardware floating point support).
 @item OSS               @tab X      @tab X
 @item PulseAudio        @tab X      @tab X
 @item SDL               @tab        @tab X
+@item Sun Audio         @tab        @tab X
 @item Video4Linux2      @tab X      @tab X
 @item VfW capture       @tab X      @tab
 @item X11 grabbing      @tab X      @tab
diff --git a/doc/indevs.texi b/doc/indevs.texi
index 6f5afaf344..c55dcb6329 100644
--- a/doc/indevs.texi
+++ b/doc/indevs.texi
@@ -1285,6 +1285,35 @@  Set the number of channels. Default is 2.
 
 @end table
 
+@section sunau
+
+Solaris/NetBSD audio input device.
+
+The filename to provide to the input device is the device node
+representing the Sun input device, and is usually set to
+@file{/dev/audio0}.
+
+For example to grab from @file{/dev/audio0} using @command{ffmpeg} use the
+command:
+@example
+ffmpeg -f sunau -i /dev/audio0 /tmp/oss.wav
+@end example
+
+@subsection Options
+
+@table @option
+
+@item buffer_samples
+Set the size of the audio buffer in samples. Default is 32.
+
+@item sample_rate
+Set the sample rate in Hz. Default is 48000.
+
+@item channels
+Set the number of channels. Default is 2.
+
+@end table
+
 @section video4linux2, v4l2
 
 Video4Linux2 input video device.
diff --git a/doc/outdevs.texi b/doc/outdevs.texi
index 60606eb6e7..5a327adbb0 100644
--- a/doc/outdevs.texi
+++ b/doc/outdevs.texi
@@ -395,6 +395,17 @@  ffmpeg -i INPUT -c:v rawvideo -pix_fmt yuv420p -window_size qcif -f sdl "SDL out
 
 sndio audio output device.
 
+@section sunau
+
+Solaris/NetBSD audio output device.
+
+@subsection Options
+@table @option
+
+@item buffer_samples
+Set the size of the audio buffer in samples. Default is 32.
+@end table
+
 @section v4l2
 
 Video4Linux2 output device.
diff --git a/libavdevice/Makefile b/libavdevice/Makefile
index 6ea62b914e..51d703d824 100644
--- a/libavdevice/Makefile
+++ b/libavdevice/Makefile
@@ -43,6 +43,8 @@  OBJS-$(CONFIG_PULSE_OUTDEV)              += pulse_audio_enc.o \
 OBJS-$(CONFIG_SDL2_OUTDEV)               += sdl2.o
 OBJS-$(CONFIG_SNDIO_INDEV)               += sndio_dec.o sndio.o
 OBJS-$(CONFIG_SNDIO_OUTDEV)              += sndio_enc.o sndio.o
+OBJS-$(CONFIG_SUNAU_INDEV)               += sunau_dec.o sunau.o
+OBJS-$(CONFIG_SUNAU_OUTDEV)              += sunau_enc.o sunau.o
 OBJS-$(CONFIG_V4L2_INDEV)                += v4l2.o v4l2-common.o timefilter.o
 OBJS-$(CONFIG_V4L2_OUTDEV)               += v4l2enc.o v4l2-common.o
 OBJS-$(CONFIG_VFWCAP_INDEV)              += vfwcap.o
diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
index 8633433254..c2fce8a4f3 100644
--- a/libavdevice/alldevices.c
+++ b/libavdevice/alldevices.c
@@ -49,6 +49,8 @@  extern AVOutputFormat ff_pulse_muxer;
 extern AVOutputFormat ff_sdl2_muxer;
 extern AVInputFormat  ff_sndio_demuxer;
 extern AVOutputFormat ff_sndio_muxer;
+extern AVInputFormat  ff_sunau_demuxer;
+extern AVOutputFormat ff_sunau_muxer;
 extern AVInputFormat  ff_v4l2_demuxer;
 extern AVOutputFormat ff_v4l2_muxer;
 extern AVInputFormat  ff_vfwcap_demuxer;
diff --git a/libavdevice/sunau.c b/libavdevice/sunau.c
new file mode 100644
index 0000000000..a707388617
--- /dev/null
+++ b/libavdevice/sunau.c
@@ -0,0 +1,102 @@ 
+/*
+ * Solaris/NetBSD play and grab interface
+ * Copyright (c) 2020 Yorick Hardy
+ * Copyright (c) 2020 Nia Alarie <nia@NetBSD.org>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+
+#include "libavutil/log.h"
+
+#include "libavcodec/avcodec.h"
+#include "avdevice.h"
+
+#include "sunau.h"
+
+int ff_sunau_audio_open(AVFormatContext *s1, int is_output,
+                          const char *audio_device)
+{
+    SunAudioData *s = s1->priv_data;
+    struct audio_info info;
+    struct audio_prinfo *prinfo;
+    int audio_fd, err;
+
+    audio_fd = avpriv_open(audio_device, is_output ? O_WRONLY : O_RDONLY);
+    if (audio_fd < 0) {
+        av_log(s1, AV_LOG_ERROR, "%s: %s\n", audio_device, av_err2str(AVERROR(errno)));
+        return AVERROR(EIO);
+    }
+
+    AUDIO_INITINFO(&info);
+
+#ifdef AUMODE_PLAY /* BSD extension */
+    info.mode = is_output ? AUMODE_PLAY : AUMODE_RECORD;
+#endif
+
+    prinfo = is_output ? &info.play : &info.record;
+
+    prinfo->encoding = AUDIO_ENCODING_LINEAR;
+    prinfo->precision = 16;
+    prinfo->sample_rate = s->sample_rate;
+    prinfo->channels = s->channels;
+
+    if ((err = ioctl(audio_fd, AUDIO_SETINFO, &info)) < 0) {
+        av_log(s1, AV_LOG_ERROR, "AUDIO_SETINFO: %s\n", av_err2str(AVERROR(errno)));
+        goto fail;
+    }
+
+    if ((err = ioctl(audio_fd, AUDIO_GETINFO, &info)) < 0) {
+        av_log(s1, AV_LOG_ERROR, "AUDIO_GETINFO: %s\n", av_err2str(AVERROR(errno)));
+        goto fail;
+    }
+
+    s->fd = audio_fd;
+#ifdef HAVE_BIGENDIAN
+    s->codec_id = AV_CODEC_ID_PCM_S16BE;
+#else
+    s->codec_id = AV_CODEC_ID_PCM_S16LE;
+#endif
+    s->precision = prinfo->precision;
+    s->sample_rate = prinfo->sample_rate;
+    s->channels = prinfo->channels;
+    s->blocksize = s->buffer_samples * prinfo->precision * prinfo->channels;
+
+    if ((s->buffer = malloc(s->blocksize)) == NULL) {
+        av_log(s1, AV_LOG_ERROR, "malloc: %s\n", av_err2str(AVERROR(errno)));
+        goto fail;
+    }
+
+    return 0;
+ fail:
+    close(audio_fd);
+    return AVERROR(EIO);
+}
+
+int ff_sunau_audio_close(SunAudioData *s)
+{
+    close(s->fd);
+    return 0;
+}
diff --git a/libavdevice/sunau.h b/libavdevice/sunau.h
new file mode 100644
index 0000000000..27dd8bec68
--- /dev/null
+++ b/libavdevice/sunau.h
@@ -0,0 +1,48 @@ 
+/*
+ * Solaris/NetBSD play and grab interface
+ * Copyright (c) 2020 Yorick Hardy
+ * Copyright (c) 2020 Nia Alarie <nia@NetBSD.org>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVDEVICE_SUNAU_H
+#define AVDEVICE_SUNAU_H
+
+#include "libavcodec/avcodec.h"
+
+#include "libavformat/avformat.h"
+
+typedef struct SunAudioData {
+    AVClass *class;
+    int fd;
+    int buffer_samples;
+    unsigned int sample_rate;
+    unsigned int channels;
+    unsigned int precision;
+    size_t blocksize;
+    enum AVCodecID codec_id;
+    uint8_t *buffer;
+    size_t buffer_ptr;
+} SunAudioData;
+
+int ff_sunau_audio_open(AVFormatContext *s1, int is_output,
+                        const char *audio_device);
+
+int ff_sunau_audio_close(SunAudioData *s);
+
+#endif /* AVDEVICE_SUNAU_H */
diff --git a/libavdevice/sunau_dec.c b/libavdevice/sunau_dec.c
new file mode 100644
index 0000000000..7157fbc6d6
--- /dev/null
+++ b/libavdevice/sunau_dec.c
@@ -0,0 +1,138 @@ 
+/*
+ * Solaris/NetBSD play and grab interface
+ * Copyright (c) 2020 Yorick Hardy
+ * Copyright (c) 2020 Nia Alarie <nia@NetBSD.org>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <stdint.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+
+#include "libavutil/internal.h"
+#include "libavutil/opt.h"
+#include "libavutil/time.h"
+
+#include "libavcodec/avcodec.h"
+
+#include "avdevice.h"
+#include "libavformat/internal.h"
+
+#include "sunau.h"
+
+static int audio_read_header(AVFormatContext *s1)
+{
+    SunAudioData *s = s1->priv_data;
+    AVStream *st;
+
+    st = avformat_new_stream(s1, NULL);
+    if (!st) {
+        return AVERROR(ENOMEM);
+    }
+
+    if (ff_sunau_audio_open(s1, 0, s1->url) < 0) {
+        return AVERROR(EIO);
+    }
+
+    /* take real parameters */
+    st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+    st->codecpar->codec_id = s->codec_id;
+    st->codecpar->sample_rate = s->sample_rate;
+    st->codecpar->channels = s->channels;
+
+    avpriv_set_pts_info(st, 64, 1, 1000000);  /* 64 bits pts in us */
+
+    return 0;
+}
+
+static int audio_read_packet(AVFormatContext *s1, AVPacket *pkt)
+{
+    SunAudioData *s = s1->priv_data;
+    struct audio_info info;
+    int ret;
+    long bdelay;
+    int64_t cur_time;
+
+    if ((ret = av_new_packet(pkt, s->blocksize)) < 0)
+        return ret;
+
+    ret = read(s->fd, pkt->data, pkt->size);
+    if (ret <= 0) {
+        av_packet_unref(pkt);
+        pkt->size = 0;
+        return ret < 0 ? AVERROR(errno) : AVERROR_EOF;
+    }
+
+    /* compute pts of the start of the packet */
+    cur_time = av_gettime();
+    bdelay = ret;
+
+#ifdef AUDIO_GETBUFINFO /* BSD extension */
+    if (ioctl(s->fd, AUDIO_GETBUFINFO, &info) == 0) {
+        bdelay += info.record.seek;
+    }
+#endif
+
+    /* subtract time represented by the number of bytes in the audio fifo */
+    cur_time -= (bdelay * 1000000LL) / (s->sample_rate * s->channels * s->precision);
+
+    /* convert to wanted units */
+    pkt->pts = cur_time;
+
+    return 0;
+}
+
+static int audio_read_close(AVFormatContext *s1)
+{
+    SunAudioData *s = s1->priv_data;
+
+    ff_sunau_audio_close(s);
+    return 0;
+}
+
+static const AVOption options[] = {
+    { "buffer_samples", "", offsetof(SunAudioData, buffer_samples), AV_OPT_TYPE_INT, {.i64 = 32},    1,     INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
+    { "sample_rate",    "", offsetof(SunAudioData, sample_rate),    AV_OPT_TYPE_INT, {.i64 = 48000}, 1000,  192000,  AV_OPT_FLAG_DECODING_PARAM },
+    { "channels",       "", offsetof(SunAudioData, channels),       AV_OPT_TYPE_INT, {.i64 = 2},     1,     12,      AV_OPT_FLAG_DECODING_PARAM },
+    { NULL },
+};
+
+static const AVClass sunau_demuxer_class = {
+    .class_name     = "Sun/NetBSD audio demuxer",
+    .item_name      = av_default_item_name,
+    .option         = options,
+    .version        = LIBAVUTIL_VERSION_INT,
+    .category       = AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
+};
+
+AVInputFormat ff_sunau_demuxer = {
+    .name           = "sunau",
+    .long_name      = NULL_IF_CONFIG_SMALL("Sun/NetBSD audio capture"),
+    .priv_data_size = sizeof(SunAudioData),
+    .read_header    = audio_read_header,
+    .read_packet    = audio_read_packet,
+    .read_close     = audio_read_close,
+    .flags          = AVFMT_NOFILE,
+    .priv_class     = &sunau_demuxer_class,
+};
diff --git a/libavdevice/sunau_enc.c b/libavdevice/sunau_enc.c
new file mode 100644
index 0000000000..f71705d9ca
--- /dev/null
+++ b/libavdevice/sunau_enc.c
@@ -0,0 +1,114 @@ 
+/*
+ * Solaris/NetBSD play and grab interface
+ * Copyright (c) 2020 Yorick Hardy
+ * Copyright (c) 2020 Nia Alarie <nia@NetBSD.org>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+
+#include "libavutil/internal.h"
+
+#include "libavcodec/avcodec.h"
+
+#include "avdevice.h"
+#include "libavformat/internal.h"
+
+#include "sunau.h"
+
+static int audio_write_header(AVFormatContext *s1)
+{
+    SunAudioData *s = s1->priv_data;
+    AVStream *st;
+
+    st = s1->streams[0];
+    s->sample_rate = st->codecpar->sample_rate;
+    s->channels = st->codecpar->channels;
+    s->codec_id = st->codecpar->codec_id;
+    return ff_sunau_audio_open(s1, 1, s1->url) < 0 ? AVERROR(EIO) : 0;
+}
+
+static int audio_write_packet(AVFormatContext *s1, AVPacket *pkt)
+{
+    SunAudioData *s = s1->priv_data;
+    unsigned int len, size = pkt->size;
+    uint8_t *buf = pkt->data;
+    int ret;
+
+    while (size > 0) {
+        len = FFMIN(s->blocksize - s->buffer_ptr, size);
+        memcpy(s->buffer + s->buffer_ptr, buf, len);
+        s->buffer_ptr += len;
+        if (s->buffer_ptr >= s->blocksize) {
+            for (;;) {
+                ret = write(s->fd, s->buffer, s->blocksize);
+                if (ret > 0)
+                    break;
+                if (ret < 0 && (errno != EAGAIN && errno != EINTR))
+                    return AVERROR(EIO);
+            }
+            s->buffer_ptr = 0;
+        }
+        buf += len;
+        size -= len;
+    }
+    return 0;
+}
+
+static int audio_write_trailer(AVFormatContext *s1)
+{
+    SunAudioData *s = s1->priv_data;
+
+    ff_sunau_audio_close(s);
+    return 0;
+}
+
+static const AVOption options[] = {
+    { "buffer_samples", "", offsetof(SunAudioData, buffer_samples), AV_OPT_TYPE_INT, {.i64 = 32}, 1, 192000, AV_OPT_FLAG_DECODING_PARAM },
+    { NULL },
+};
+
+static const AVClass sunau_muxer_class = {
+    .class_name     = "Sun/NetBSD audio muxer",
+    .item_name      = av_default_item_name,
+    .option         = options,
+    .version        = LIBAVUTIL_VERSION_INT,
+    .category       = AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
+};
+
+AVOutputFormat ff_sunau_muxer = {
+    .name           = "sunau",
+    .long_name      = NULL_IF_CONFIG_SMALL("Sun/NetBSD audio playback"),
+    .priv_data_size = sizeof(SunAudioData),
+    /* XXX: we may need to support higher precisions in the future, but
+       right now this is what the kernel can handle natively */
+    /* XXX: find better solution with "preinit" method, needed also in
+       other formats */
+    .audio_codec    = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE),
+    .video_codec    = AV_CODEC_ID_NONE,
+    .write_header   = audio_write_header,
+    .write_packet   = audio_write_packet,
+    .write_trailer  = audio_write_trailer,
+    .flags          = AVFMT_NOFILE,
+    .priv_class     = &sunau_muxer_class,
+};