nxplayer: add mp3 offload playback demo

Mask: Only parse ID3v2 header simple.

Usage:
    nxplayer
    device pcm0p
    play /stream/1.mp3

Signed-off-by: qiaohaijiao1 <qiaohaijiao1@xiaomi.com>
This commit is contained in:
qiaohaijiao1 2022-11-30 09:27:36 +08:00 committed by Xiang Xiao
parent 5ae46ab6a7
commit 05eadd1bd6
5 changed files with 526 additions and 51 deletions

View File

@ -39,6 +39,14 @@
* Public Type Declarations
****************************************************************************/
struct nxplayer_dec_ops_s
{
int format;
CODE int (*pre_parse)(int fd, FAR uint32_t *samplerate,
FAR uint8_t *chans, FAR uint8_t *bps);
CODE int (*fill_data)(int fd, FAR struct ap_buffer_s *apb);
};
/* This structure describes the internal state of the NxPlayer */
struct nxplayer_s
@ -72,6 +80,8 @@ struct nxplayer_s
uint16_t treble; /* Treble as a whole % */
uint16_t bass; /* Bass as a whole % */
#endif
FAR const struct nxplayer_dec_ops_s *ops;
};
typedef int (*nxplayer_func)(FAR struct nxplayer_s *pplayer, char *pargs);
@ -480,6 +490,53 @@ int nxplayer_settreble(FAR struct nxplayer_s *pplayer, uint8_t treble);
int nxplayer_systemreset(FAR struct nxplayer_s *pplayer);
#endif
/****************************************************************************
* Name: nxplayer_parse_mp3
*
* Performs pre-process when play mp3 file.
* Parse samplerate, channels, bps.
*
* Input Parameters:
* pplayer - Pointer to the context to initialize
*
* Returned Value:
* OK if file parsed successfully.
*
****************************************************************************/
int nxplayer_parse_mp3(int fd, FAR uint32_t *samplerate,
FAR uint8_t *chans, FAR uint8_t *bps);
/****************************************************************************
* Name: nxplayer_fill_mp3
*
* Performs read mp3 frame to apb buffer
*
* Input Parameters:
* pplayer - Pointer to the context to initialize
*
* Returned Value:
* OK if file read successfully.
*
****************************************************************************/
int nxplayer_fill_mp3(int fd, FAR struct ap_buffer_s *apb);
/****************************************************************************
* Name: nxplayer_fill_pcm
*
* Performs read pcm file to apb buffer
*
* Input Parameters:
* pplayer - Pointer to the context to initialize
*
* Returned Value:
* OK if file read successfully.
*
****************************************************************************/
int nxplayer_fill_pcm(int fd, FAR struct ap_buffer_s *apb);
#undef EXTERN
#ifdef __cplusplus
}

View File

@ -23,6 +23,8 @@ include $(APPDIR)/Make.defs
# NxPlayer Library
CSRCS = nxplayer.c
CSRCS += nxplayer_mp3.c
CSRCS += nxplayer_pcm.c
ifneq ($(CONFIG_NXPLAYER_COMMAND_LINE),)
PROGNAME = nxplayer

View File

@ -88,6 +88,10 @@ struct nxplayer_ext_fmt_s
int nxplayer_getmidisubformat(int fd);
#endif
#ifdef CONFIG_AUDIO_FORMAT_MP3
int nxplayer_getmp3subformat(int fd);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
@ -99,7 +103,7 @@ static const struct nxplayer_ext_fmt_s g_known_ext[] =
{ "ac3", AUDIO_FMT_AC3, NULL },
#endif
#ifdef CONFIG_AUDIO_FORMAT_MP3
{ "mp3", AUDIO_FMT_MP3, NULL },
{ "mp3", AUDIO_FMT_MP3, nxplayer_getmp3subformat },
#endif
#ifdef CONFIG_AUDIO_FORMAT_DTS
{ "dts", AUDIO_FMT_DTS, NULL },
@ -121,6 +125,21 @@ static const struct nxplayer_ext_fmt_s g_known_ext[] =
static const int g_known_ext_count = sizeof(g_known_ext) /
sizeof(struct nxplayer_ext_fmt_s);
static const struct nxplayer_dec_ops_s g_dec_ops[] =
{
{
AUDIO_FMT_MP3,
nxplayer_parse_mp3,
nxplayer_fill_mp3
},
{
AUDIO_FMT_PCM,
NULL,
nxplayer_fill_pcm
}
};
#endif /* CONFIG_NXPLAYER_FMT_FROM_EXT */
/****************************************************************************
@ -501,6 +520,11 @@ int nxplayer_getmidisubformat(int fd)
}
#endif
int nxplayer_getmp3subformat(int fd)
{
return AUDIO_SUBFMT_PCM_MP3;
}
/****************************************************************************
* Name: nxplayer_fmtfromextension
*
@ -608,6 +632,8 @@ static int nxplayer_mediasearch(FAR struct nxplayer_s *pplayer,
static int nxplayer_readbuffer(FAR struct nxplayer_s *pplayer,
FAR struct ap_buffer_s *apb)
{
int ret;
/* Validate the file is still open. It will be closed automatically when
* we encounter the end of file (or, perhaps, a read error that we cannot
* handle.
@ -622,65 +648,17 @@ static int nxplayer_readbuffer(FAR struct nxplayer_s *pplayer,
return -ENODATA;
}
/* Read data into the buffer. */
apb->nbytes = read(pplayer->fd, apb->samp, apb->nmaxbytes);
apb->curbyte = 0;
apb->flags = 0;
#ifdef CONFIG_NXPLAYER_HTTP_STREAMING_SUPPORT
/* read data up to nmaxbytes from network */
while (0 < apb->nbytes && apb->nbytes < apb->nmaxbytes)
ret = pplayer->ops->fill_data(pplayer->fd, apb);
if (ret < 0)
{
int n = apb->nmaxbytes - apb->nbytes;
int ret = read(pplayer->fd, &apb->samp[apb->nbytes], n);
if (0 >= ret)
{
break;
}
apb->nbytes += ret;
}
#endif
if (apb->nbytes < apb->nmaxbytes)
{
#if defined (CONFIG_DEBUG_AUDIO_INFO) || defined (CONFIG_DEBUG_AUDIO_ERROR)
int errcode = errno;
audinfo("Closing audio file, nbytes=%d errcode=%d\n",
apb->nbytes, errcode);
#endif
/* End of file or read error.. We are finished with this file in any
* event.
*/
close(pplayer->fd);
pplayer->fd = -1;
/* Set a flag to indicate that this is the final buffer in the stream */
apb->flags |= AUDIO_APB_FINAL;
#ifdef CONFIG_DEBUG_AUDIO_ERROR
/* Was this a file read error */
if (apb->nbytes == 0 && errcode != 0)
{
DEBUGASSERT(errcode > 0);
auderr("ERROR: fread failed: %d\n", errcode);
}
#endif
}
/* Return OK to indicate that the buffer should be passed through to the
* audio device. This does not necessarily indicate that data was read
* correctly.
*/
return OK;
}
@ -1150,6 +1128,7 @@ err_out:
pplayer->dev_fd = -1; /* Mark device as closed */
mq_close(pplayer->mq); /* Close the message queue */
mq_unlink(pplayer->mqname); /* Unlink the message queue */
pplayer->ops = NULL; /* Clear offload parser */
pplayer->state = NXPLAYER_STATE_IDLE; /* Go to IDLE */
sem_post(&pplayer->sem); /* Release the semaphore */
@ -1780,6 +1759,7 @@ static int nxplayer_playinternal(FAR struct nxplayer_s *pplayer,
#endif
int tmpsubfmt = AUDIO_FMT_UNDEF;
int ret;
int c;
DEBUGASSERT(pplayer != NULL);
DEBUGASSERT(pfilename != NULL);
@ -1877,6 +1857,26 @@ static int nxplayer_playinternal(FAR struct nxplayer_s *pplayer,
goto err_out_nodev;
}
for (c = 0; c < sizeof(g_dec_ops) / sizeof(g_dec_ops[0]); c++)
{
if (g_dec_ops[c].format == filefmt)
{
pplayer->ops = &g_dec_ops[c];
break;
}
}
if (!pplayer->ops)
{
goto err_out;
}
if (pplayer->ops->pre_parse)
{
ret = pplayer->ops->pre_parse(pplayer->fd, &samprate,
&nchannels, &bpsamp);
}
/* Try to reserve the device */
#ifdef CONFIG_AUDIO_MULTI_SESSION
@ -1906,6 +1906,7 @@ static int nxplayer_playinternal(FAR struct nxplayer_s *pplayer,
cap_desc.caps.ac_controls.hw[0] = samprate;
cap_desc.caps.ac_controls.b[3] = samprate >> 16;
cap_desc.caps.ac_controls.b[2] = bpsamp;
cap_desc.caps.ac_subtype = filefmt;
ioctl(pplayer->dev_fd, AUDIOIOC_CONFIGURE, (unsigned long)&cap_desc);
}

View File

@ -0,0 +1,311 @@
/****************************************************************************
* apps/system/nxplayer/nxplayer_mp3.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <nuttx/audio/audio.h>
#include "system/nxplayer.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define ID3V2_BIT_MASK 0x7F
/****************************************************************************
* Private Type Declarations
****************************************************************************/
const static uint16_t g_mpa_freq_tab[3] =
{
44100, 48000, 32000
};
const static uint16_t g_mpa_bitrate_tab[2][3][15] =
{
{
{
0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448
},
{
0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384
},
{
0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320
}
},
{
{
0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256
},
{
0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160
},
{
0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160
}
}
};
/****************************************************************************
* Private Functions
****************************************************************************/
static int nxplayer_check_mpeg(uint32_t header)
{
/* header */
if ((header & 0xffe00000) != 0xffe00000)
{
return -EINVAL;
}
/* version check */
if ((header & (3 << 19)) == 1 << 19)
{
return -EINVAL;
}
/* layer check */
if ((header & (3 << 17)) == 0)
{
return -EINVAL;
}
/* bit rate */
if ((header & (0xf << 12)) == 0xf << 12)
{
return -EINVAL;
}
/* frequency */
if ((header & (3 << 10)) == 3 << 10)
{
return -EINVAL;
}
return 0;
}
static int nxplayer_parse_mpeg(uint32_t header, FAR uint32_t *samplerate,
FAR uint8_t *chans, FAR uint8_t *bps)
{
int sample_rate;
int frame_size;
int padding;
int mpeg25;
int sr_idx;
int br_idx;
int layer;
int mode;
int lsf;
int ret;
ret = nxplayer_check_mpeg(header);
if (ret < 0)
{
return ret;
}
if (header & (1 << 20))
{
lsf = (header & (1 << 19)) ? 0 : 1;
mpeg25 = 0;
}
else
{
lsf = 1;
mpeg25 = 1;
}
layer = 4 - ((header >> 17) & 3);
br_idx = (header >> 12) & 0xf;
sr_idx = (header >> 10) & 3;
padding = (header >> 9) & 1;
mode = (header >> 6) & 3;
if (sr_idx >= sizeof(g_mpa_freq_tab) / sizeof(g_mpa_freq_tab[0]) ||
br_idx >= 0xf)
{
return -EINVAL;
}
sample_rate = g_mpa_freq_tab[sr_idx] >> (lsf + mpeg25);
if (br_idx != 0)
{
frame_size = g_mpa_bitrate_tab[lsf][layer - 1][br_idx];
switch (layer)
{
case 1:
frame_size = (frame_size * 12000) / sample_rate;
frame_size = (frame_size + padding) * 4;
break;
case 2:
frame_size = (frame_size * 144000) / sample_rate;
frame_size += padding;
break;
default:
case 3:
frame_size = (frame_size * 144000) / (sample_rate << lsf);
frame_size += padding;
break;
}
}
else
{
/* if no frame size computed, signal it */
return -EINVAL;
}
if (samplerate)
{
*samplerate = sample_rate;
}
if (chans)
{
*chans = mode == 3 ? 1 : 2;
}
if (bps)
{
*bps = 16;
}
return frame_size;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nxplayer_parse_mp3
*
* nxplayer_parse_mp3() parse mp3 header, get samplerate, channels, bps.
*
****************************************************************************/
int nxplayer_parse_mp3(int fd, FAR uint32_t *samplerate,
FAR uint8_t *chans, FAR uint8_t *bps)
{
uint32_t mpa_header;
uint8_t buffer[10];
off_t position;
int ret;
ret = read(fd, buffer, sizeof(buffer));
if (ret < sizeof(buffer))
{
return -ENODATA;
}
position = (buffer[6] & ID3V2_BIT_MASK) * 0x200000 +
(buffer[7] & ID3V2_BIT_MASK) * 0x4000 +
(buffer[8] & ID3V2_BIT_MASK) * 0x80 +
(buffer[9] & ID3V2_BIT_MASK) +
sizeof(buffer);
lseek(fd, position, SEEK_SET);
read(fd, buffer, 4);
mpa_header = buffer[0] << 24 |
buffer[1] << 16 |
buffer[2] << 8 |
buffer[3];
ret = nxplayer_parse_mpeg(mpa_header, samplerate, chans, bps);
if (ret < 0)
{
return ret;
}
lseek(fd, position, SEEK_SET);
return OK;
}
/****************************************************************************
* Name: nxplayer_fill_mp3
*
* nxplayer_fill_mp3 fill mp3 data into apb buffer.
*
****************************************************************************/
int nxplayer_fill_mp3(int fd, FAR struct ap_buffer_s *apb)
{
uint32_t mpa_header;
uint8_t header[16];
int h_size = 4;
int b_size;
int size;
int ret;
ret = read(fd, header, h_size);
if (ret < h_size)
{
return -ENODATA;
}
mpa_header = header[0] << 24 |
header[1] << 16 |
header[2] << 8 |
header[3];
size = nxplayer_parse_mpeg(mpa_header, NULL, NULL, NULL);
if (size < 0)
{
return size;
}
memcpy(apb->samp, header, h_size);
b_size = size - h_size;
ret = read(fd, apb->samp + h_size, b_size + 8);
if (ret < b_size)
{
return -ENODATA;
}
lseek(fd, -8, SEEK_CUR);
apb->nbytes = size + 8;
apb->curbyte = 0;
apb->flags = 0;
return OK;
}

View File

@ -0,0 +1,104 @@
/****************************************************************************
* apps/system/nxplayer/nxplayer_pcm.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <debug.h>
#include <nuttx/audio/audio.h>
#include "system/nxplayer.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nxplayer_fill_pcm
*
* nxplayer_fill_pcm fill pcm data into apb buffer.
*
****************************************************************************/
int nxplayer_fill_pcm(int fd, FAR struct ap_buffer_s *apb)
{
/* Read data into the buffer. */
apb->nbytes = read(fd, apb->samp, apb->nmaxbytes);
apb->curbyte = 0;
apb->flags = 0;
#ifdef CONFIG_NXPLAYER_HTTP_STREAMING_SUPPORT
/* read data up to nmaxbytes from network */
while (0 < apb->nbytes && apb->nbytes < apb->nmaxbytes)
{
int n = apb->nmaxbytes - apb->nbytes;
int ret = read(fd, &apb->samp[apb->nbytes], n);
if (0 >= ret)
{
break;
}
apb->nbytes += ret;
}
#endif
if (apb->nbytes < apb->nmaxbytes)
{
#if defined (CONFIG_DEBUG_AUDIO_INFO) || defined (CONFIG_DEBUG_AUDIO_ERROR)
int errcode = errno;
audinfo("Closing audio file, nbytes=%d errcode=%d\n",
apb->nbytes, errcode);
#endif
/* Set a flag to indicate that this is the final buffer in the stream */
apb->flags |= AUDIO_APB_FINAL;
#ifdef CONFIG_DEBUG_AUDIO_ERROR
/* Was this a file read error */
if (apb->nbytes == 0 && errcode != 0)
{
DEBUGASSERT(errcode > 0);
auderr("ERROR: fread failed: %d\n", errcode);
}
#endif
return -ENODATA;
}
/* Return OK to indicate that the buffer should be passed through to the
* audio device. This does not necessarily indicate that data was read
* correctly.
*/
return OK;
}