250 lines
7.7 KiB
C
250 lines
7.7 KiB
C
/*
|
|
* NUTTX input and output
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* NUTTX input and output: input
|
|
*
|
|
* This avdevice decoder can capture audio from an NUTTX device.
|
|
*
|
|
* The capture period is set to the lower value available for the device,
|
|
* which gives a low latency suitable for real-time capture.
|
|
*/
|
|
|
|
#include <poll.h>
|
|
|
|
#include "libavformat/demux.h"
|
|
#include "libavformat/internal.h"
|
|
#include "libavutil/internal.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/mem.h"
|
|
|
|
#include "nuttx.h"
|
|
|
|
#define AVFMT_FLAG_CODEC_READY 0x20000
|
|
|
|
static int nuttx_control_message(struct AVFormatContext *s1,
|
|
int type, void *data, size_t data_size)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
|
|
switch (type) {
|
|
case AV_APP_TO_DEV_GET_POLLFD: {
|
|
struct pollfd *poll = data;
|
|
|
|
if (!data || data_size < sizeof(struct pollfd) || !priv->running)
|
|
return AVERROR(EINVAL);
|
|
|
|
poll[0].fd = priv->mq;
|
|
poll[0].events = POLLIN;
|
|
|
|
return 1;
|
|
}
|
|
case AV_APP_TO_DEV_POLL_AVAILABLE: {
|
|
int ret;
|
|
|
|
ret = ff_nuttx_poll_available(priv);
|
|
avdevice_dev_to_app_control_message(s1, AV_DEV_TO_APP_BUFFER_READABLE, NULL, 0);
|
|
|
|
return ret;
|
|
}
|
|
case AV_APP_TO_DEV_START: {
|
|
priv->stopped = false;
|
|
avdevice_dev_to_app_control_message(s1, AV_DEV_TO_APP_STATE_CHANGED, NULL, 0);
|
|
|
|
return 0;
|
|
}
|
|
case AV_APP_TO_DEV_STOP: {
|
|
priv->stopped = true;
|
|
avdevice_dev_to_app_control_message(s1, AV_DEV_TO_APP_STATE_CHANGED, NULL, 0);
|
|
|
|
return 0;
|
|
}
|
|
case AV_APP_TO_DEV_MUTE: {
|
|
priv->mute = true;
|
|
return 0;
|
|
}
|
|
case AV_APP_TO_DEV_UNMUTE: {
|
|
priv->mute = false;
|
|
return 0;
|
|
}
|
|
case AV_APP_TO_DEV_SET_PARAMETER:
|
|
return ff_nuttx_set_parameter(priv, data);
|
|
case AV_APP_TO_DEV_DUMP:
|
|
snprintf(data, data_size, "%d|%d|%d|%d|%zu|%d",
|
|
priv->running, priv->draining,
|
|
priv->period_bytes, priv->periods, dq_count(&priv->bufferq), priv->mq);
|
|
return 0;
|
|
}
|
|
|
|
return AVERROR(ENOSYS);
|
|
}
|
|
|
|
static int nuttx_read_header(AVFormatContext *s1)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
AVStream *st;
|
|
int ret;
|
|
|
|
if (!(s1->flags & AVFMT_FLAG_CODEC_READY))
|
|
return 0;
|
|
|
|
st = avformat_new_stream(s1, NULL);
|
|
if (!st)
|
|
return AVERROR(ENOMEM);
|
|
|
|
if (s1->audio_codec_id != AV_CODEC_ID_NONE)
|
|
priv->codec = s1->audio_codec_id;
|
|
|
|
if (priv->codec == AV_CODEC_ID_NONE)
|
|
priv->codec = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE);
|
|
|
|
if (s1->flags & AVFMT_FLAG_NONBLOCK)
|
|
priv->nonblock = true;
|
|
|
|
ret = ff_nuttx_init(priv, s1->url, false);
|
|
if (ret < 0) {
|
|
ff_remove_stream(s1, st);
|
|
return ret;
|
|
}
|
|
|
|
ret = ff_nuttx_open(s1->priv_data);
|
|
if (ret < 0) {
|
|
ff_remove_stream(s1, st);
|
|
return ret;
|
|
}
|
|
|
|
priv->timestamp = 0;
|
|
|
|
/* take real parameters */
|
|
st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
|
|
st->codecpar->codec_id = priv->codec;
|
|
st->codecpar->sample_rate = priv->sample_rate;
|
|
av_channel_layout_copy(&st->codecpar->ch_layout, &priv->ch_layout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nuttx_read_close(AVFormatContext *s1)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
AVStream *st = NULL;
|
|
|
|
if (!s1->streams)
|
|
return AVERROR(EINVAL);
|
|
|
|
st = s1->streams[0];
|
|
|
|
ff_nuttx_deinit(s1->priv_data);
|
|
|
|
ff_remove_stream(s1, st);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nuttx_read_packet(AVFormatContext *s1, AVPacket *pkt)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
uint32_t samples;
|
|
int ret;
|
|
|
|
if (priv->stopped)
|
|
return AVERROR_EOF;
|
|
|
|
ret = av_new_packet(pkt, priv->period_bytes);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ff_nuttx_read_data(priv, pkt->data, priv->period_bytes, &samples);
|
|
if (ret < 0) {
|
|
av_packet_unref(pkt);
|
|
return ret;
|
|
}
|
|
|
|
pkt->size = ret;
|
|
pkt->pts = priv->timestamp;
|
|
priv->timestamp += samples > 0 ? samples : ret / priv->sample_bytes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nuttx_get_device_list(struct AVFormatContext *s, struct AVDeviceInfoList *device_list)
|
|
{
|
|
if (!device_list)
|
|
return AVERROR(EINVAL);
|
|
|
|
return ff_nuttx_get_device_list(device_list, false);
|
|
}
|
|
|
|
static int nuttx_capbility_query_ranges(struct AVOptionRanges **ranges_, void *obj,
|
|
const char *key, int flags)
|
|
{
|
|
struct AVFormatContext *s1 = obj;
|
|
struct AVOptionRanges *ranges;
|
|
int ret;
|
|
|
|
if (!strcmp(key, "control_message")) {
|
|
ranges = av_mallocz(sizeof(struct AVOptionRanges));
|
|
if (!ranges)
|
|
return AVERROR(ENOMEM);;
|
|
|
|
ranges->range = (AVOptionRange **)nuttx_control_message;
|
|
ranges->nb_ranges = 0;
|
|
*ranges_ = ranges;
|
|
return 0;
|
|
}
|
|
|
|
return ff_nuttx_capbility_query_ranges(ranges_, s1->url, key, flags, false);
|
|
}
|
|
|
|
#define OFFSET(x) offsetof(NuttxPriv, x)
|
|
#define FLAGS AV_OPT_FLAG_DECODING_PARAM|AV_OPT_FLAG_AUDIO_PARAM
|
|
static const AVOption options[] = {
|
|
{ "periods", "", OFFSET(periods), AV_OPT_TYPE_INT, {.i64 = 4}, 0, INT_MAX, FLAGS },
|
|
{ "period_bytes", "", OFFSET(period_bytes), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, FLAGS },
|
|
{ "period_time", "", OFFSET(period_time), AV_OPT_TYPE_INT, {.i64 = 20}, 0, INT_MAX, FLAGS },
|
|
{ "codec", "", OFFSET(codec), AV_OPT_TYPE_INT, {.i64 = AV_CODEC_ID_NONE}, AV_CODEC_ID_NONE, INT_MAX, FLAGS },
|
|
{ "format", "", OFFSET(format), AV_OPT_TYPE_SAMPLE_FMT, {.i64 = AV_SAMPLE_FMT_NONE}, AV_SAMPLE_FMT_NONE, AV_SAMPLE_FMT_NB - 1, FLAGS },
|
|
{ "sample_rate", "", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64 = 48000}, 1, INT_MAX, FLAGS },
|
|
{ "ch_layout", "", OFFSET(ch_layout), AV_OPT_TYPE_CHLAYOUT, {.str = "stereo" }, 0, 0, FLAGS },
|
|
{ NULL },
|
|
};
|
|
|
|
static const AVClass nuttx_demuxer_class = {
|
|
.class_name = "NUTTX indev",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
.category = AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
|
|
.query_ranges = nuttx_capbility_query_ranges,
|
|
};
|
|
|
|
const FFInputFormat ff_nuttx_demuxer = {
|
|
.p.name = "nuttx",
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("NUTTX audio input"),
|
|
.p.flags = AVFMT_NOFILE,
|
|
.p.priv_class = &nuttx_demuxer_class,
|
|
.priv_data_size = sizeof(NuttxPriv),
|
|
.read_header = nuttx_read_header,
|
|
.read_packet = nuttx_read_packet,
|
|
.read_close = nuttx_read_close,
|
|
.get_device_list = nuttx_get_device_list,
|
|
};
|