diff --git a/configure b/configure index a036873682..009d26f779 100755 --- a/configure +++ b/configure @@ -940,6 +940,7 @@ CONFIG_LIST=" libopencore_amrwb libopencv libopenjpeg + libpulse librtmp libschroedinger libspeex @@ -1458,6 +1459,7 @@ libcdio_indev_deps="libcdio" libdc1394_indev_deps="libdc1394" oss_indev_deps_any="soundcard_h sys_soundcard_h" oss_outdev_deps_any="soundcard_h sys_soundcard_h" +pulse_indev_deps="libpulse" sndio_indev_deps="sndio_h" sndio_outdev_deps="sndio_h" v4l_indev_deps="linux_videodev_h" @@ -2894,6 +2896,7 @@ enabled libopencore_amrnb && require libopencore_amrnb opencore-amrnb/interf_de enabled libopencore_amrwb && require libopencore_amrwb opencore-amrwb/dec_if.h D_IF_init -lopencore-amrwb enabled libopencv && require_pkg_config opencv opencv/cv.h cvCreateImageHeader enabled libopenjpeg && require libopenjpeg openjpeg.h opj_version -lopenjpeg +enabled libpulse && require_pkg_config libpulse-simple pulse/simple.h pa_simple_new enabled librtmp && require_pkg_config librtmp librtmp/rtmp.h RTMP_Socket enabled libschroedinger && require_pkg_config schroedinger-1.0 schroedinger/schro.h schro_init enabled libspeex && require libspeex speex/speex.h speex_decoder_init -lspeex @@ -3179,6 +3182,7 @@ echo "libopencore-amrnb support ${libopencore_amrnb-no}" echo "libopencore-amrwb support ${libopencore_amrwb-no}" echo "libopencv support ${libopencv-no}" echo "libopenjpeg enabled ${libopenjpeg-no}" +echo "libpulse enabled ${libpulse-no}" echo "librtmp enabled ${librtmp-no}" echo "libschroedinger enabled ${libschroedinger-no}" echo "libspeex enabled ${libspeex-no}" diff --git a/libavdevice/Makefile b/libavdevice/Makefile index d8a5945549..892b8a6893 100644 --- a/libavdevice/Makefile +++ b/libavdevice/Makefile @@ -16,6 +16,7 @@ OBJS-$(CONFIG_FBDEV_INDEV) += fbdev.o OBJS-$(CONFIG_JACK_INDEV) += jack_audio.o OBJS-$(CONFIG_OSS_INDEV) += oss_audio.o OBJS-$(CONFIG_OSS_OUTDEV) += oss_audio.o +OBJS-$(CONFIG_PULSE_INDEV) += pulse.o OBJS-$(CONFIG_SNDIO_INDEV) += sndio_common.o sndio_dec.o OBJS-$(CONFIG_SNDIO_OUTDEV) += sndio_common.o sndio_enc.o OBJS-$(CONFIG_V4L2_INDEV) += v4l2.o diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c index a1a333f454..e65644bc1a 100644 --- a/libavdevice/alldevices.c +++ b/libavdevice/alldevices.c @@ -45,6 +45,7 @@ void avdevice_register_all(void) REGISTER_INDEV (FBDEV, fbdev); REGISTER_INDEV (JACK, jack); REGISTER_INOUTDEV (OSS, oss); + REGISTER_INDEV (PULSE, pulse); REGISTER_INOUTDEV (SNDIO, sndio); REGISTER_INDEV (V4L2, v4l2); #if FF_API_V4L diff --git a/libavdevice/pulse.c b/libavdevice/pulse.c new file mode 100644 index 0000000000..f0d0571d99 --- /dev/null +++ b/libavdevice/pulse.c @@ -0,0 +1,192 @@ +/* + * Pulseaudio input + * Copyright (c) 2011 Luca Barbato + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Pulseaudio input + * @author Luca Barbato + * + * This avdevice decoder allows to capture audio from a Pulseaudio device using + * the simple api. + * + */ + +#include +#include +#include + +#include "libavformat/avformat.h" +#include "libavutil/opt.h" + +#define DEFAULT_CODEC_ID AV_NE(CODEC_ID_PCM_S16BE, CODEC_ID_PCM_S16LE) + +typedef struct PulseData { + AVClass *class; + char *server; + char *name; + char *dev; + char *stream_name; + int sample_rate; + int channels; + int frame_size; + pa_simple *s; + int64_t pts; +} PulseData; + +static pa_sample_format_t codec_id_to_pulse_format(int codec_id) { + switch(codec_id) { + case CODEC_ID_PCM_U8: return PA_SAMPLE_U8; + case CODEC_ID_PCM_ALAW: return PA_SAMPLE_ALAW; + case CODEC_ID_PCM_MULAW: return PA_SAMPLE_ULAW; + case CODEC_ID_PCM_S16LE: return PA_SAMPLE_S16LE; + case CODEC_ID_PCM_S16BE: return PA_SAMPLE_S16BE; + case CODEC_ID_PCM_F32LE: return PA_SAMPLE_FLOAT32LE; + case CODEC_ID_PCM_F32BE: return PA_SAMPLE_FLOAT32BE; + case CODEC_ID_PCM_S32LE: return PA_SAMPLE_S32LE; + case CODEC_ID_PCM_S32BE: return PA_SAMPLE_S32BE; + case CODEC_ID_PCM_S24LE: return PA_SAMPLE_S24LE; + case CODEC_ID_PCM_S24BE: return PA_SAMPLE_S24BE; + default: return PA_SAMPLE_INVALID; + } +} + +static av_cold int pulse_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + PulseData *pd = s->priv_data; + AVStream *st; + int ret; + enum CodecID codec_id = + s->audio_codec_id == CODEC_ID_NONE ? DEFAULT_CODEC_ID : s->audio_codec_id; + const pa_sample_spec ss = { codec_id_to_pulse_format(codec_id), + pd->sample_rate, + pd->channels }; + + pa_buffer_attr attr = { -1 }; + + st = avformat_new_stream(s, NULL); + + if (!st) { + av_log(s, AV_LOG_ERROR, "Cannot add stream\n"); + return AVERROR(ENOMEM); + } + + attr.fragsize = pd->frame_size * 4; + + pd->s = pa_simple_new(pd->server, pd->name, + PA_STREAM_RECORD, + pd->dev, pd->stream_name, &ss, + NULL, &attr, &ret); + if (!pd->s) { + av_log(s, AV_LOG_ERROR, "pa_simple_new failed: %s\n", + pa_strerror(ret)); + return AVERROR(EIO); + } + /* take real parameters */ + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = codec_id; + st->codec->sample_rate = pd->sample_rate; + st->codec->channels = pd->channels; + av_set_pts_info(st, 64, 1, 1000000); /* 64 bits pts in us */ + + return 0; +} + +static int pulse_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + PulseData *pd = s->priv_data; + int res; + pa_usec_t latency, cur; + uint64_t frame_duration = + (pd->frame_size*1000000LL)/(pd->sample_rate * pd->channels); + + if (av_new_packet(pkt, pd->frame_size) < 0) { + return AVERROR(ENOMEM); + } + + cur = pa_rtclock_now(); + + if ((pa_simple_read(pd->s, pkt->data, pkt->size, &res)) < 0) { + av_log(s, AV_LOG_ERROR, "pa_simple_read failed: %s\n", + pa_strerror(res)); + av_free_packet(pkt); + return AVERROR(EIO); + } + + if ((latency = pa_simple_get_latency(pd->s, &res)) == (pa_usec_t) -1) { + av_log(s, AV_LOG_ERROR, "pa_simple_get_latency() failed: %s\n", + pa_strerror(res)); + return AVERROR(EIO); + } + + if (!pd->pts) { + pd->pts -= latency; + } + + pd->pts += frame_duration; + + av_log(s, AV_LOG_DEBUG, "%"PRId64" time %"PRId64"," + " latency %"PRId64", %"PRId64"\n", + av_gettime(), cur, latency, pd->pts); + + pkt->pts = pd->pts; + + return 0; +} + +static av_cold int pulse_close(AVFormatContext *s) +{ + PulseData *pd = s->priv_data; + pa_simple_free(pd->s); + return 0; +} + +#define OFFSET(a) offsetof(PulseData, a) +#define D AV_OPT_FLAG_DECODING_PARAM + +static const AVOption options[] = { + { "server", "pulse server name", OFFSET(server), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, D }, + { "name", "application name", OFFSET(name), AV_OPT_TYPE_STRING, {.str = "libav"}, 0, 0, D }, + { "dev", "device to use", OFFSET(dev), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, D }, + { "stream_name", "stream description", OFFSET(stream_name), AV_OPT_TYPE_STRING, {.str = "record"}, 0, 0, D }, + { "sample_rate", "", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.dbl = 48000}, 1, INT_MAX, D }, + { "channels", "", OFFSET(channels), AV_OPT_TYPE_INT, {.dbl = 2}, 1, INT_MAX, D }, + { "frame_size", "", OFFSET(frame_size), AV_OPT_TYPE_INT, {.dbl = 1024}, 1, INT_MAX, D }, + { NULL }, +}; + +static const AVClass pulse_demuxer_class = { + .class_name = "Pulse demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_pulse_demuxer = { + .name = "pulse", + .long_name = NULL_IF_CONFIG_SMALL("Pulse audio input"), + .priv_data_size = sizeof(PulseData), + .read_header = pulse_read_header, + .read_packet = pulse_read_packet, + .read_close = pulse_close, + .flags = AVFMT_NOFILE, + .priv_class = &pulse_demuxer_class, +};