354 lines
11 KiB
C
354 lines
11 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: output
|
|
*
|
|
* This avdevice encoder can play audio to an NUTTX device.
|
|
*
|
|
* The playback period is set to the lower value available for the device,
|
|
* which gives a low latency suitable for real-time playback.
|
|
*/
|
|
|
|
#include <poll.h>
|
|
|
|
#include "libavformat/internal.h"
|
|
#include "libavformat/mux.h"
|
|
#include "libavcodec/bsf.h"
|
|
#include "libavutil/internal.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/time.h"
|
|
#include "libavutil/frame.h"
|
|
|
|
#include "nuttx.h"
|
|
|
|
static int nuttx_capbility_query_ranges(struct AVOptionRanges **ranges, void *obj,
|
|
const char *key, int flags)
|
|
{
|
|
struct AVFormatContext *s1 = obj;
|
|
|
|
return ff_nuttx_capbility_query_ranges(ranges, s1->url, key, flags, true);
|
|
}
|
|
|
|
static int nuttx_init(struct AVFormatContext *s1)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
int ret;
|
|
|
|
ret = ff_nuttx_init(priv, s1->url, true);
|
|
return ret < 0 ? ret : 1;
|
|
}
|
|
|
|
static void nuttx_deinit(struct AVFormatContext *s1)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
|
|
if (priv->lastpkt)
|
|
av_packet_free(&priv->lastpkt);
|
|
|
|
ff_nuttx_deinit(s1->priv_data);
|
|
}
|
|
|
|
static int nuttx_write_header(AVFormatContext *s1)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
AVStream *st = s1->streams[0];
|
|
int ret;
|
|
|
|
if (s1->nb_streams != 1 || s1->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
|
|
return AVERROR(EINVAL);
|
|
|
|
if (s1->flags & AVFMT_FLAG_NONBLOCK)
|
|
priv->nonblock = true;
|
|
|
|
priv->codec = st->codecpar->codec_id;
|
|
priv->sample_rate = st->codecpar->sample_rate;
|
|
priv->format = st->codecpar->format;
|
|
priv->timestamp = 0;
|
|
av_channel_layout_copy(&priv->ch_layout, &st->codecpar->ch_layout);
|
|
|
|
ret = ff_nuttx_open(s1->priv_data);
|
|
if (ret >= 0)
|
|
avpriv_set_pts_info(st, 64, 1, priv->sample_rate);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int nuttx_write_trailer(struct AVFormatContext *s1)
|
|
{
|
|
FFStream *const sti = ffstream(s1->streams[0]);
|
|
NuttxPriv *priv = s1->priv_data;
|
|
|
|
ff_nuttx_close(priv);
|
|
priv->timestamp = 0;
|
|
if (sti->bsfc) {
|
|
av_bsf_flush(sti->bsfc);
|
|
av_bsf_free(&sti->bsfc);
|
|
sti->bitstream_checked = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int nuttx_write_lastpacket(AVFormatContext *s1)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
int ret;
|
|
|
|
ret = ff_nuttx_write_data(priv, priv->lastpkt->data, priv->lastpkt->size);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
priv->timestamp += ret / priv->sample_bytes;
|
|
|
|
priv->lastpkt->data += ret;
|
|
priv->lastpkt->size -= ret;
|
|
if (priv->lastpkt->size)
|
|
return AVERROR(EAGAIN);
|
|
|
|
av_packet_free(&priv->lastpkt);
|
|
return 0;
|
|
}
|
|
|
|
static int nuttx_write_packet(AVFormatContext *s1, AVPacket *pkt)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
int ret;
|
|
|
|
if (priv->stopped) {
|
|
if (priv->lastpkt)
|
|
av_packet_free(&priv->lastpkt);
|
|
return AVERROR_EOF;
|
|
}
|
|
|
|
if (priv->lastpkt)
|
|
return nuttx_write_lastpacket(s1);
|
|
|
|
if (!pkt)
|
|
return 0;
|
|
|
|
ret = ff_nuttx_write_data(priv, pkt->data, pkt->size);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
priv->timestamp += ret / priv->sample_bytes;
|
|
|
|
if (ret != pkt->size) {
|
|
priv->lastpkt = av_packet_clone(pkt);
|
|
priv->lastpkt->data += ret;
|
|
priv->lastpkt->size -= ret;
|
|
return AVERROR(EAGAIN);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nuttx_control_message(struct AVFormatContext *s1, int type,
|
|
void *data, size_t data_size)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
AVPacket pkt = {0};
|
|
|
|
switch (type) {
|
|
case AV_APP_TO_DEV_SET_VOLUME:
|
|
if (!data)
|
|
return AVERROR(EINVAL);
|
|
|
|
return ff_nuttx_set_volume(s1, priv, *((double *)data));
|
|
case AV_APP_TO_DEV_MUTE:
|
|
return ff_nuttx_set_mute(s1, priv, true);
|
|
case AV_APP_TO_DEV_UNMUTE:
|
|
return ff_nuttx_set_mute(s1, priv, false);
|
|
case AV_APP_TO_DEV_TOGGLE_MUTE:
|
|
return ff_nuttx_set_mute(s1, priv, !priv->mute);
|
|
case AV_APP_TO_DEV_GET_VOLUME:
|
|
snprintf(data, data_size, "vol:%f", priv->volume);
|
|
return ff_nuttx_notify_changed(s1, priv, true);
|
|
case AV_APP_TO_DEV_GET_MUTE:
|
|
return ff_nuttx_notify_changed(s1, priv, false);
|
|
case AV_APP_TO_DEV_GET_POSITION: {
|
|
long ret = ff_nuttx_get_position(priv);
|
|
if (ret < 0)
|
|
return ret;
|
|
snprintf(data, data_size, "%ld", ret);
|
|
return 0;
|
|
}
|
|
case AV_APP_TO_DEV_GET_POLLFD: {
|
|
struct pollfd *poll = data;
|
|
|
|
if (!data || data_size < sizeof(struct pollfd))
|
|
return AVERROR(EINVAL);
|
|
|
|
poll[0].fd = priv->mq;
|
|
poll[0].events = POLLIN;
|
|
|
|
return 1;
|
|
}
|
|
case AV_APP_TO_DEV_POLL_AVAILABLE: {
|
|
enum AVDevToAppMessageType t;
|
|
int ret;
|
|
|
|
ret = ff_nuttx_poll_available(priv);
|
|
t = ret == AVERROR_EXIT ? AV_DEV_TO_APP_BUFFER_DRAINED: AV_DEV_TO_APP_BUFFER_WRITABLE;
|
|
|
|
avdevice_dev_to_app_control_message(s1, t, NULL, 0);
|
|
return 0;
|
|
}
|
|
case AV_APP_TO_DEV_START: {
|
|
priv->stopped = false;
|
|
avdevice_dev_to_app_control_message(s1, AV_DEV_TO_APP_STATE_CHANGED, &type, 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, &type, 0);
|
|
|
|
return 0;
|
|
}
|
|
case AV_APP_TO_DEV_PAUSE:
|
|
return ff_nuttx_pause(priv);
|
|
case AV_APP_TO_DEV_PLAY:
|
|
return ff_nuttx_resume(priv);
|
|
case AV_APP_TO_DEV_FLUSH:
|
|
/* drop lastpkt */
|
|
if (priv->lastpkt)
|
|
av_packet_free(&priv->lastpkt);
|
|
|
|
return ff_nuttx_flush(priv);
|
|
case AV_APP_TO_DEV_SET_PARAMETER:
|
|
return ff_nuttx_set_parameter(priv, data);
|
|
case AV_APP_TO_DEV_DRAIN:
|
|
if (ff_nuttx_get_position(priv) < 0)
|
|
return AVERROR(ENOTSUP);
|
|
|
|
pkt.flags = AV_PKT_FLAG_EVT_EOS;
|
|
return nuttx_write_packet(s1, &pkt);
|
|
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_write_frame(AVFormatContext *s1, int stream_index,
|
|
AVFrame **frame, unsigned flags)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
AVPacket pkt;
|
|
|
|
/* ff_nuttx_open() should have accepted only supported formats */
|
|
if ((flags & AV_WRITE_UNCODED_FRAME_QUERY))
|
|
return av_sample_fmt_is_planar(s1->streams[stream_index]->codecpar->format) ?
|
|
AVERROR(EINVAL) : 0;
|
|
|
|
/* set only used fields */
|
|
pkt.data = (*frame)->data[0];
|
|
pkt.size = (*frame)->nb_samples * priv->sample_bytes;
|
|
pkt.dts = (*frame)->pkt_dts;
|
|
pkt.duration = (*frame)->duration;
|
|
return nuttx_write_packet(s1, &pkt);
|
|
}
|
|
|
|
static void nuttx_get_output_timestamp(struct AVFormatContext *s1, int stream,
|
|
int64_t *ts, int64_t *lat)
|
|
{
|
|
NuttxPriv *priv = s1->priv_data;
|
|
long latency;
|
|
|
|
latency = ff_nuttx_get_latency(priv);
|
|
latency = FFMAX(latency, 0);
|
|
|
|
if (priv->lastpkt)
|
|
latency += priv->lastpkt->size / priv->sample_bytes;
|
|
|
|
*lat = latency;
|
|
*ts = priv->timestamp - latency;
|
|
}
|
|
|
|
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, true);
|
|
}
|
|
|
|
static int nuttx_check_bitstream(struct AVFormatContext *s, struct AVStream *st, const AVPacket *pkt)
|
|
{
|
|
const struct CodecBsf {
|
|
enum AVCodecID id;
|
|
const char *name;
|
|
} codec_bsf_table[] = {
|
|
{AV_CODEC_ID_AAC, "aac_rawtoadts"},
|
|
{AV_CODEC_ID_OPUS, "raw2tlv"}
|
|
};
|
|
|
|
for (size_t i = 0; i < FF_ARRAY_ELEMS(codec_bsf_table); i++) {
|
|
if (codec_bsf_table[i].id == st->codecpar->codec_id) {
|
|
av_log(s, AV_LOG_DEBUG, "%s bsf is added\n", codec_bsf_table[i].name);
|
|
return ff_stream_add_bitstream_filter(st, codec_bsf_table[i].name, NULL);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define OFFSET(x) offsetof(NuttxPriv, x)
|
|
#define FLAGS AV_OPT_FLAG_ENCODING_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},
|
|
{ NULL },
|
|
};
|
|
|
|
static const AVClass nuttx_muxer_class = {
|
|
.class_name = "NUTTX outdev",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
.category = AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
|
|
.query_ranges = nuttx_capbility_query_ranges,
|
|
};
|
|
|
|
const FFOutputFormat ff_nuttx_muxer = {
|
|
.p.name = "nuttx",
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("NUTTX audio output"),
|
|
.priv_data_size = sizeof(NuttxPriv),
|
|
.p.audio_codec = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE),
|
|
.p.video_codec = AV_CODEC_ID_NONE,
|
|
.init = nuttx_init,
|
|
.deinit = nuttx_deinit,
|
|
.write_header = nuttx_write_header,
|
|
.write_packet = nuttx_write_packet,
|
|
.write_trailer = nuttx_write_trailer,
|
|
.control_message = nuttx_control_message,
|
|
.write_uncoded_frame = nuttx_write_frame,
|
|
.get_output_timestamp = nuttx_get_output_timestamp,
|
|
.get_device_list = nuttx_get_device_list,
|
|
.check_bitstream = nuttx_check_bitstream,
|
|
.p.flags = AVFMT_NOFILE | AVFMT_TS_NONSTRICT | AVFMT_NOTIMESTAMPS,
|
|
.p.priv_class = &nuttx_muxer_class,
|
|
};
|