Merge branch 'main' of https://git.sr.ht/~jacqueline/tangara-fw
commit
039272455a
@ -0,0 +1,73 @@ |
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
Licensed 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. |
@ -0,0 +1,8 @@ |
||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||
# |
||||
# SPDX-License-Identifier: GPL-3.0-only |
||||
|
||||
idf_component_register( |
||||
SRCS "flac.c" |
||||
INCLUDE_DIRS "include" |
||||
) |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,297 @@ |
||||
/*
|
||||
* libfoxenflac -- Tiny FLAC Decoder Library |
||||
* Copyright (C) 2018-2022 Andreas Stöckel |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program 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 General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
/**
|
||||
* @file flac.h |
||||
* |
||||
* Provides a decoder for FLAC (Free Lossless Audio Codec). |
||||
* |
||||
* @author Andreas Stöckel |
||||
*/ |
||||
|
||||
#ifndef FOXEN_FLAC_H |
||||
#define FOXEN_FLAC_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#ifndef FX_EXPORT |
||||
#if __EMSCRIPTEN__ |
||||
#import <emscripten.h> |
||||
#define FX_EXPORT EMSCRIPTEN_KEEPALIVE |
||||
#else |
||||
#define FX_EXPORT |
||||
#endif /* __EMSCRIPTEN__ */ |
||||
#endif /* FX_EXPORT */ |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* Value returned by the fx_flac_get_streaminfo() method if the given streaminfo |
||||
* key is invalid. |
||||
*/ |
||||
#define FLAC_INVALID_METADATA_KEY 0x7FFFFFFFFFFFFFFFULL |
||||
|
||||
/**
|
||||
* Maximum number of channels that can be encoded in a FLAC stream. |
||||
*/ |
||||
#define FLAC_MAX_CHANNEL_COUNT 8U |
||||
|
||||
/**
|
||||
* Maximum block size that can be used if the stream is encoded in the FLAC |
||||
* Subset format and the sample rate is smaller than 48000 kHz. |
||||
*/ |
||||
#define FLAC_SUBSET_MAX_BLOCK_SIZE_48KHZ 4608U |
||||
|
||||
/**
|
||||
* Maximum block size than can always be safely used if the stream is encoded |
||||
* in the FLAC Subset format. |
||||
*/ |
||||
#define FLAC_SUBSET_MAX_BLOCK_SIZE 16384U |
||||
|
||||
/**
|
||||
* Maximum block size in samples that can be used in a FLAC stream. |
||||
*/ |
||||
#define FLAC_MAX_BLOCK_SIZE 65535U |
||||
|
||||
/**
|
||||
* Opaque struct representing a FLAC decoder. |
||||
*/ |
||||
struct fx_flac; |
||||
|
||||
/**
|
||||
* Typedef for the fx_flac struct. |
||||
*/ |
||||
typedef struct fx_flac fx_flac_t; |
||||
|
||||
/**
|
||||
* Enum representing the state of a FLAC decoder instance. |
||||
*/ |
||||
typedef enum { |
||||
/**
|
||||
* The decoder is in an error state; the decoder cannot recover from this |
||||
* error. This error may for example occur if the data in the stream is |
||||
* invalid, or the stream has a format that is outside the maximum specs |
||||
* that are supported by the decoder. Call fx_flac_reset() and start anew! |
||||
*/ |
||||
FLAC_ERR = -1, |
||||
|
||||
/**
|
||||
* The decoder is currently in its initial state, fx_flac_process() has not |
||||
* been called. |
||||
*/ |
||||
FLAC_INIT = 0, |
||||
|
||||
/**
|
||||
* The decoder found the beginning of the metadata packet! |
||||
*/ |
||||
FLAC_IN_METADATA = 1, |
||||
|
||||
/**
|
||||
* The decoder is done reading the current metadata block, this may be |
||||
* followed by more metadata blocks, in which case the state is reset to |
||||
* FLAC_IN_METADATA. |
||||
*/ |
||||
FLAC_END_OF_METADATA = 2, |
||||
|
||||
/**
|
||||
* The decoder is currently searching for an audio frame. |
||||
*/ |
||||
FLAC_SEARCH_FRAME = 3, |
||||
|
||||
/**
|
||||
* The decoder is currently inside the stream of audio frames. |
||||
*/ |
||||
FLAC_IN_FRAME = 4, |
||||
|
||||
/**
|
||||
* The decoder successfully decoded an entire frame. Write the data to the |
||||
* client. |
||||
*/ |
||||
FLAC_DECODED_FRAME = 5, |
||||
|
||||
/**
|
||||
* The decoder reached the end of a block. |
||||
*/ |
||||
FLAC_END_OF_FRAME = 6 |
||||
} fx_flac_state_t; |
||||
|
||||
/**
|
||||
* Enum used in fx_flac_get_streaminfo() to query metadata about the stream. |
||||
*/ |
||||
typedef enum { |
||||
FLAC_KEY_MIN_BLOCK_SIZE = 0, |
||||
FLAC_KEY_MAX_BLOCK_SIZE = 1, |
||||
FLAC_KEY_MIN_FRAME_SIZE = 2, |
||||
FLAC_KEY_MAX_FRAME_SIZE = 3, |
||||
FLAC_KEY_SAMPLE_RATE = 4, |
||||
FLAC_KEY_N_CHANNELS = 5, |
||||
FLAC_KEY_SAMPLE_SIZE = 6, |
||||
FLAC_KEY_N_SAMPLES = 7, |
||||
FLAC_KEY_MD5_SUM_0 = 128, |
||||
FLAC_KEY_MD5_SUM_1 = 129, |
||||
FLAC_KEY_MD5_SUM_2 = 130, |
||||
FLAC_KEY_MD5_SUM_3 = 131, |
||||
FLAC_KEY_MD5_SUM_4 = 132, |
||||
FLAC_KEY_MD5_SUM_5 = 133, |
||||
FLAC_KEY_MD5_SUM_6 = 134, |
||||
FLAC_KEY_MD5_SUM_7 = 135, |
||||
FLAC_KEY_MD5_SUM_8 = 136, |
||||
FLAC_KEY_MD5_SUM_9 = 137, |
||||
FLAC_KEY_MD5_SUM_A = 138, |
||||
FLAC_KEY_MD5_SUM_B = 139, |
||||
FLAC_KEY_MD5_SUM_C = 140, |
||||
FLAC_KEY_MD5_SUM_D = 141, |
||||
FLAC_KEY_MD5_SUM_E = 142, |
||||
FLAC_KEY_MD5_SUM_F = 143, |
||||
} fx_flac_streaminfo_key_t; |
||||
|
||||
/**
|
||||
* Returns the size of the FLAC decoder instance in bytes. This assumes that the |
||||
* FLAC audio that is being decoded uses the maximum settings, i.e. the largest |
||||
* bit depth and block size. See fx_flac_init() regarding parameters. |
||||
* |
||||
* @return zero if the given parameters are out of range, the number of bytes |
||||
* required to hold the FLAC decoder structure otherwise. |
||||
*/ |
||||
FX_EXPORT uint32_t fx_flac_size(uint32_t max_block_size, uint8_t max_channels); |
||||
|
||||
/**
|
||||
* Initializes the FLAC decoder at the given memory location. Each decoder can |
||||
* decode exactly one stream at a time. |
||||
* |
||||
* @param mem is a pointer at the memory region at which the FLAC decoder should |
||||
* store its private data. The memory region must be at last as large as |
||||
* indicated by fx_flac_size(). May be NULL, in which case NULL is returned. |
||||
* @param max_block_size is the maximum block size for which the FLAC instance |
||||
* will provide a buffer. For streams in the Subset format (which is used per |
||||
* default in most FLAC encoders), max_block_size should can be set to 4608 if |
||||
* the sample rate is <= 48000kHz, otherwise, for larger sample rates, |
||||
* max_block_size must be set to 16384. |
||||
* @param max_channels is the maximum number of channels that will be decoded. |
||||
* @return a pointer at the FLAC decoder instance; note that this pointer may be |
||||
* different from what was passed to mem. However, you may still pass the |
||||
* original `mem` as `inst` parameter to other functions. Returns NULL if the |
||||
* input pointer is NULL or the given parameters are invalid. |
||||
*/ |
||||
FX_EXPORT fx_flac_t *fx_flac_init(void *mem, uint16_t max_block_size, |
||||
uint8_t max_channels); |
||||
|
||||
/**
|
||||
* Macro which calls malloc to allocate memory for a new fx_flac instance. The |
||||
* returned pointer must be freed using free. Returns NULL if the allocation |
||||
* fails or the given parameters are invalid. |
||||
* |
||||
* Note that this code is implemented as a macro to prevent explicitly having |
||||
* a dependency on malloc while still providing a convenient allocation routine. |
||||
*/ |
||||
#define FX_FLAC_ALLOC(max_block_size, max_channels) \ |
||||
(fx_flac_size((max_block_size), (max_channels)) == 0U) \
|
||||
? NULL \
|
||||
: fx_flac_init(malloc(fx_flac_size((max_block_size), (max_channels))), \
|
||||
(max_block_size), (max_channels)) |
||||
|
||||
/**
|
||||
* Returns a new fx_flac instance that is sufficient to decode FLAC streams in |
||||
* the FLAC Subset format with DAT parameters, i.e. up to 48 kHz, and two |
||||
* channels. This will allocate about 40 kiB of memory. |
||||
*/ |
||||
#define FX_FLAC_ALLOC_SUBSET_FORMAT_DAT() \ |
||||
FX_FLAC_ALLOC(FLAC_SUBSET_MAX_BLOCK_SIZE_48KHZ, 2U) |
||||
|
||||
/**
|
||||
* Returns a new fx_flac instance that is sufficient to decode FLAC streams in |
||||
* the FLAC Subset format. This will allocate about 1.5 MiB of memory. |
||||
*/ |
||||
#define FX_FLAC_ALLOC_SUBSET_FORMAT_ANY() \ |
||||
FX_FLAC_ALLOC(FLAC_SUBSET_MAX_BLOCK_SIZE, FLAC_MAX_CHANNEL_COUNT) |
||||
|
||||
/**
|
||||
* Returns a new fx_flac instance that is sufficient to decode any valid FLAC |
||||
* stream. Note that this will allocate between 2-3 MiB of memory. |
||||
*/ |
||||
#define FX_FLAC_ALLOC_DEFAULT() \ |
||||
FX_FLAC_ALLOC(FLAC_MAX_BLOCK_SIZE, FLAC_MAX_CHANNEL_COUNT) |
||||
|
||||
/**
|
||||
* Resets the FLAC decoder. |
||||
* |
||||
* @param inst is the FLAC decoder that should be reset. |
||||
*/ |
||||
FX_EXPORT void fx_flac_reset(fx_flac_t *inst); |
||||
|
||||
/**
|
||||
* Returns the current decoder state. |
||||
* |
||||
* @param inst is the FLAC decoder instance for which the state should be |
||||
* returned. |
||||
* @return the current state of the decoder. |
||||
*/ |
||||
FX_EXPORT fx_flac_state_t fx_flac_get_state(const fx_flac_t *inst); |
||||
|
||||
/**
|
||||
* Returns metadata about the FLAC stream that is currently being parsed. This |
||||
* function may only be called if the decoder is in the state |
||||
* FLAC_END_OF_METADATA or greater, otherwise the result may be undefined |
||||
* (it will likely return zero for most of the metadata keys). |
||||
* |
||||
* @param inst is a pointer at the FLAC decoder instance for which the metadata |
||||
* should be retrieved. |
||||
* @param key is the metadata that should be retrieved. |
||||
* @return the requested metadata value or FLAC_INVALID_METADATA_KEY if the |
||||
* given key is unknown. |
||||
*/ |
||||
FX_EXPORT int64_t fx_flac_get_streaminfo(const fx_flac_t *inst, |
||||
fx_flac_streaminfo_key_t key); |
||||
|
||||
/**
|
||||
* Decodes the given raw FLAC data; the given data must be RAW FLAC data as |
||||
* specified in the FLAC format specification https://xiph.org/flac/format.html
|
||||
* This function will always return right after the decoder transitions to a new |
||||
* relevant state. |
||||
* |
||||
* @param inst is the decoder instance. |
||||
* @param in is a pointer at the encoded bytestream. |
||||
* @param in_len is a pointer at a integer containing the number of valid bytes |
||||
* in "in". After the function returns, in will contain the number of bytes that |
||||
* were actually read. This number may be zero if the decoder is in the FLAC_ERR |
||||
* or FLAC_STREAM_DONE state, or the internal buffers are full and need to be |
||||
* flushed to the provided output first. |
||||
* @param out is a pointer at a memory region that will accept the decoded |
||||
* interleaved audio data. Samples are decoded as 32-bit signed integer; the |
||||
* minimum and maximum value will depend on the original bit depth of the audio |
||||
* stored in the bitstream. If this is NULL, the decoder will silently discard |
||||
* the output. |
||||
* @param out_len is a pointer at an integer containing the number of available |
||||
* signed 32-bit integers at the memory address pointed at by out. After the |
||||
* function returns, this value will contain the number of samples that were |
||||
* written. If this is NULL, the deocder will silently discard the output. |
||||
* @return the current state of the decoder. If the state transitions to |
||||
* FLAC_END_OF_METADATA, FLAC_END_OF_FRAME or FLAC_END_OF_STREAM this function |
||||
* will return immediately; only the data up to the point causing the transition |
||||
* has been read. |
||||
*/ |
||||
FX_EXPORT fx_flac_state_t fx_flac_process(fx_flac_t *inst, const uint8_t *in, |
||||
uint32_t *in_len, int32_t *out, |
||||
uint32_t *out_len); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif /* FOXEN_FLAC_H */ |
@ -0,0 +1,8 @@ |
||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||
# |
||||
# SPDX-License-Identifier: GPL-3.0-only |
||||
|
||||
idf_component_register( |
||||
SRCS "stb_vorbis.c" |
||||
INCLUDE_DIRS "include" |
||||
) |
@ -0,0 +1,418 @@ |
||||
// Ogg Vorbis audio decoder - v1.22 - public domain
|
||||
// http://nothings.org/stb_vorbis/
|
||||
//
|
||||
// Original version written by Sean Barrett in 2007.
|
||||
//
|
||||
// Originally sponsored by RAD Game Tools. Seeking implementation
|
||||
// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker,
|
||||
// Elias Software, Aras Pranckevicius, and Sean Barrett.
|
||||
//
|
||||
// LICENSE
|
||||
//
|
||||
// See end of file for license information.
|
||||
//
|
||||
// Limitations:
|
||||
//
|
||||
// - floor 0 not supported (used in old ogg vorbis files pre-2004)
|
||||
// - lossless sample-truncation at beginning ignored
|
||||
// - cannot concatenate multiple vorbis streams
|
||||
// - sample positions are 32-bit, limiting seekable 192Khz
|
||||
// files to around 6 hours (Ogg supports 64-bit)
|
||||
//
|
||||
// Feature contributors:
|
||||
// Dougall Johnson (sample-exact seeking)
|
||||
//
|
||||
// Bugfix/warning contributors:
|
||||
// Terje Mathisen Niklas Frykholm Andy Hill
|
||||
// Casey Muratori John Bolton Gargaj
|
||||
// Laurent Gomila Marc LeBlanc Ronny Chevalier
|
||||
// Bernhard Wodo Evan Balster github:alxprd
|
||||
// Tom Beaumont Ingo Leitgeb Nicolas Guillemot
|
||||
// Phillip Bennefall Rohit Thiago Goulart
|
||||
// github:manxorist Saga Musix github:infatum
|
||||
// Timur Gagiev Maxwell Koo Peter Waller
|
||||
// github:audinowho Dougall Johnson David Reid
|
||||
// github:Clownacy Pedro J. Estebanez Remi Verschelde
|
||||
// AnthoFoxo github:morlat Gabriel Ravier
|
||||
//
|
||||
// Partial history:
|
||||
// 1.22 - 2021-07-11 - various small fixes
|
||||
// 1.21 - 2021-07-02 - fix bug for files with no comments
|
||||
// 1.20 - 2020-07-11 - several small fixes
|
||||
// 1.19 - 2020-02-05 - warnings
|
||||
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
|
||||
// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure)
|
||||
// 1.16 - 2019-03-04 - fix warnings
|
||||
// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found
|
||||
// 1.14 - 2018-02-11 - delete bogus dealloca usage
|
||||
// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully)
|
||||
// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files
|
||||
// 1.11 - 2017-07-23 - fix MinGW compilation
|
||||
// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory
|
||||
// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version
|
||||
// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame
|
||||
// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const
|
||||
// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson)
|
||||
// some crash fixes when out of memory or with corrupt files
|
||||
// fix some inappropriately signed shifts
|
||||
// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant
|
||||
// 1.04 - 2014-08-27 - fix missing const-correct case in API
|
||||
// 1.03 - 2014-08-07 - warning fixes
|
||||
// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows
|
||||
// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct)
|
||||
// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel;
|
||||
// (API change) report sample rate for decode-full-file funcs
|
||||
//
|
||||
// See end of file for full version history.
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// HEADER BEGINS HERE
|
||||
//
|
||||
|
||||
#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H |
||||
#define STB_VORBIS_INCLUDE_STB_VORBIS_H |
||||
|
||||
#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) |
||||
#define STB_VORBIS_NO_STDIO 1 |
||||
#endif |
||||
|
||||
#ifndef STB_VORBIS_NO_STDIO |
||||
#include <stdio.h> |
||||
#endif |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/////////// THREAD SAFETY
|
||||
|
||||
// Individual stb_vorbis* handles are not thread-safe; you cannot decode from
|
||||
// them from multiple threads at the same time. However, you can have multiple
|
||||
// stb_vorbis* handles and decode from them independently in multiple thrads.
|
||||
|
||||
|
||||
/////////// MEMORY ALLOCATION
|
||||
|
||||
// normally stb_vorbis uses malloc() to allocate memory at startup,
|
||||
// and alloca() to allocate temporary memory during a frame on the
|
||||
// stack. (Memory consumption will depend on the amount of setup
|
||||
// data in the file and how you set the compile flags for speed
|
||||
// vs. size. In my test files the maximal-size usage is ~150KB.)
|
||||
//
|
||||
// You can modify the wrapper functions in the source (setup_malloc,
|
||||
// setup_temp_malloc, temp_malloc) to change this behavior, or you
|
||||
// can use a simpler allocation model: you pass in a buffer from
|
||||
// which stb_vorbis will allocate _all_ its memory (including the
|
||||
// temp memory). "open" may fail with a VORBIS_outofmem if you
|
||||
// do not pass in enough data; there is no way to determine how
|
||||
// much you do need except to succeed (at which point you can
|
||||
// query get_info to find the exact amount required. yes I know
|
||||
// this is lame).
|
||||
//
|
||||
// If you pass in a non-NULL buffer of the type below, allocation
|
||||
// will occur from it as described above. Otherwise just pass NULL
|
||||
// to use malloc()/alloca()
|
||||
|
||||
typedef struct |
||||
{ |
||||
char *alloc_buffer; |
||||
int alloc_buffer_length_in_bytes; |
||||
} stb_vorbis_alloc; |
||||
|
||||
|
||||
/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES
|
||||
|
||||
typedef struct stb_vorbis stb_vorbis; |
||||
|
||||
typedef struct |
||||
{ |
||||
unsigned int sample_rate; |
||||
int channels; |
||||
|
||||
unsigned int setup_memory_required; |
||||
unsigned int setup_temp_memory_required; |
||||
unsigned int temp_memory_required; |
||||
|
||||
int max_frame_size; |
||||
} stb_vorbis_info; |
||||
|
||||
typedef struct |
||||
{ |
||||
char *vendor; |
||||
|
||||
int comment_list_length; |
||||
char **comment_list; |
||||
} stb_vorbis_comment; |
||||
|
||||
// get general information about the file
|
||||
extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); |
||||
|
||||
// get ogg comments
|
||||
extern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f); |
||||
|
||||
// get the last error detected (clears it, too)
|
||||
extern int stb_vorbis_get_error(stb_vorbis *f); |
||||
|
||||
// close an ogg vorbis file and free all memory in use
|
||||
extern void stb_vorbis_close(stb_vorbis *f); |
||||
|
||||
// this function returns the offset (in samples) from the beginning of the
|
||||
// file that will be returned by the next decode, if it is known, or -1
|
||||
// otherwise. after a flush_pushdata() call, this may take a while before
|
||||
// it becomes valid again.
|
||||
// NOT WORKING YET after a seek with PULLDATA API
|
||||
extern int stb_vorbis_get_sample_offset(stb_vorbis *f); |
||||
|
||||
// returns the current seek point within the file, or offset from the beginning
|
||||
// of the memory buffer. In pushdata mode it returns 0.
|
||||
extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); |
||||
|
||||
/////////// PUSHDATA API
|
||||
|
||||
#ifndef STB_VORBIS_NO_PUSHDATA_API |
||||
|
||||
// this API allows you to get blocks of data from any source and hand
|
||||
// them to stb_vorbis. you have to buffer them; stb_vorbis will tell
|
||||
// you how much it used, and you have to give it the rest next time;
|
||||
// and stb_vorbis may not have enough data to work with and you will
|
||||
// need to give it the same data again PLUS more. Note that the Vorbis
|
||||
// specification does not bound the size of an individual frame.
|
||||
|
||||
extern stb_vorbis *stb_vorbis_open_pushdata( |
||||
const unsigned char * datablock, int datablock_length_in_bytes, |
||||
int *datablock_memory_consumed_in_bytes, |
||||
int *error, |
||||
const stb_vorbis_alloc *alloc_buffer); |
||||
// create a vorbis decoder by passing in the initial data block containing
|
||||
// the ogg&vorbis headers (you don't need to do parse them, just provide
|
||||
// the first N bytes of the file--you're told if it's not enough, see below)
|
||||
// on success, returns an stb_vorbis *, does not set error, returns the amount of
|
||||
// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes;
|
||||
// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed
|
||||
// if returns NULL and *error is VORBIS_need_more_data, then the input block was
|
||||
// incomplete and you need to pass in a larger block from the start of the file
|
||||
|
||||
extern int stb_vorbis_decode_frame_pushdata( |
||||
stb_vorbis *f, |
||||
const unsigned char *datablock, int datablock_length_in_bytes, |
||||
int *channels, // place to write number of float * buffers
|
||||
float ***output, // place to write float ** array of float * buffers
|
||||
int *samples // place to write number of output samples
|
||||
); |
||||
// decode a frame of audio sample data if possible from the passed-in data block
|
||||
//
|
||||
// return value: number of bytes we used from datablock
|
||||
//
|
||||
// possible cases:
|
||||
// 0 bytes used, 0 samples output (need more data)
|
||||
// N bytes used, 0 samples output (resynching the stream, keep going)
|
||||
// N bytes used, M samples output (one frame of data)
|
||||
// note that after opening a file, you will ALWAYS get one N-bytes,0-sample
|
||||
// frame, because Vorbis always "discards" the first frame.
|
||||
//
|
||||
// Note that on resynch, stb_vorbis will rarely consume all of the buffer,
|
||||
// instead only datablock_length_in_bytes-3 or less. This is because it wants
|
||||
// to avoid missing parts of a page header if they cross a datablock boundary,
|
||||
// without writing state-machiney code to record a partial detection.
|
||||
//
|
||||
// The number of channels returned are stored in *channels (which can be
|
||||
// NULL--it is always the same as the number of channels reported by
|
||||
// get_info). *output will contain an array of float* buffers, one per
|
||||
// channel. In other words, (*output)[0][0] contains the first sample from
|
||||
// the first channel, and (*output)[1][0] contains the first sample from
|
||||
// the second channel.
|
||||
//
|
||||
// *output points into stb_vorbis's internal output buffer storage; these
|
||||
// buffers are owned by stb_vorbis and application code should not free
|
||||
// them or modify their contents. They are transient and will be overwritten
|
||||
// once you ask for more data to get decoded, so be sure to grab any data
|
||||
// you need before then.
|
||||
|
||||
extern void stb_vorbis_flush_pushdata(stb_vorbis *f); |
||||
// inform stb_vorbis that your next datablock will not be contiguous with
|
||||
// previous ones (e.g. you've seeked in the data); future attempts to decode
|
||||
// frames will cause stb_vorbis to resynchronize (as noted above), and
|
||||
// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it
|
||||
// will begin decoding the _next_ frame.
|
||||
//
|
||||
// if you want to seek using pushdata, you need to seek in your file, then
|
||||
// call stb_vorbis_flush_pushdata(), then start calling decoding, then once
|
||||
// decoding is returning you data, call stb_vorbis_get_sample_offset, and
|
||||
// if you don't like the result, seek your file again and repeat.
|
||||
#endif |
||||
|
||||
|
||||
////////// PULLING INPUT API
|
||||
|
||||
#ifndef STB_VORBIS_NO_PULLDATA_API |
||||
// This API assumes stb_vorbis is allowed to pull data from a source--
|
||||
// either a block of memory containing the _entire_ vorbis stream, or a
|
||||
// FILE * that you or it create, or possibly some other reading mechanism
|
||||
// if you go modify the source to replace the FILE * case with some kind
|
||||
// of callback to your code. (But if you don't support seeking, you may
|
||||
// just want to go ahead and use pushdata.)
|
||||
|
||||
#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) |
||||
extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); |
||||
#endif |
||||
#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) |
||||
extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); |
||||
#endif |
||||
// decode an entire file and output the data interleaved into a malloc()ed
|
||||
// buffer stored in *output. The return value is the number of samples
|
||||
// decoded, or -1 if the file could not be opened or was not an ogg vorbis file.
|
||||
// When you're done with it, just free() the pointer returned in *output.
|
||||
|
||||
extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, |
||||
int *error, const stb_vorbis_alloc *alloc_buffer); |
||||
// create an ogg vorbis decoder from an ogg vorbis stream in memory (note
|
||||
// this must be the entire stream!). on failure, returns NULL and sets *error
|
||||
|
||||
#ifndef STB_VORBIS_NO_STDIO |
||||
extern stb_vorbis * stb_vorbis_open_filename(const char *filename, |
||||
int *error, const stb_vorbis_alloc *alloc_buffer); |
||||
// create an ogg vorbis decoder from a filename via fopen(). on failure,
|
||||
// returns NULL and sets *error (possibly to VORBIS_file_open_failure).
|
||||
|
||||
extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, |
||||
int *error, const stb_vorbis_alloc *alloc_buffer); |
||||
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
|
||||
// the _current_ seek point (ftell). on failure, returns NULL and sets *error.
|
||||
// note that stb_vorbis must "own" this stream; if you seek it in between
|
||||
// calls to stb_vorbis, it will become confused. Moreover, if you attempt to
|
||||
// perform stb_vorbis_seek_*() operations on this file, it will assume it
|
||||
// owns the _entire_ rest of the file after the start point. Use the next
|
||||
// function, stb_vorbis_open_file_section(), to limit it.
|
||||
|
||||
extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, |
||||
int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); |
||||
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
|
||||
// the _current_ seek point (ftell); the stream will be of length 'len' bytes.
|
||||
// on failure, returns NULL and sets *error. note that stb_vorbis must "own"
|
||||
// this stream; if you seek it in between calls to stb_vorbis, it will become
|
||||
// confused.
|
||||
#endif |
||||
|
||||
extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); |
||||
extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); |
||||
// these functions seek in the Vorbis file to (approximately) 'sample_number'.
|
||||
// after calling seek_frame(), the next call to get_frame_*() will include
|
||||
// the specified sample. after calling stb_vorbis_seek(), the next call to
|
||||
// stb_vorbis_get_samples_* will start with the specified sample. If you
|
||||
// do not need to seek to EXACTLY the target sample when using get_samples_*,
|
||||
// you can also use seek_frame().
|
||||
|
||||
extern int stb_vorbis_seek_start(stb_vorbis *f); |
||||
// this function is equivalent to stb_vorbis_seek(f,0)
|
||||
|
||||
extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); |
||||
extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); |
||||
// these functions return the total length of the vorbis stream
|
||||
|
||||
extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); |
||||
// decode the next frame and return the number of samples. the number of
|
||||
// channels returned are stored in *channels (which can be NULL--it is always
|
||||
// the same as the number of channels reported by get_info). *output will
|
||||
// contain an array of float* buffers, one per channel. These outputs will
|
||||
// be overwritten on the next call to stb_vorbis_get_frame_*.
|
||||
//
|
||||
// You generally should not intermix calls to stb_vorbis_get_frame_*()
|
||||
// and stb_vorbis_get_samples_*(), since the latter calls the former.
|
||||
|
||||
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION |
||||
extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); |
||||
extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); |
||||
#endif |
||||
// decode the next frame and return the number of *samples* per channel.
|
||||
// Note that for interleaved data, you pass in the number of shorts (the
|
||||
// size of your array), but the return value is the number of samples per
|
||||
// channel, not the total number of samples.
|
||||
//
|
||||
// The data is coerced to the number of channels you request according to the
|
||||
// channel coercion rules (see below). You must pass in the size of your
|
||||
// buffer(s) so that stb_vorbis will not overwrite the end of the buffer.
|
||||
// The maximum buffer size needed can be gotten from get_info(); however,
|
||||
// the Vorbis I specification implies an absolute maximum of 4096 samples
|
||||
// per channel.
|
||||
|
||||
// Channel coercion rules:
|
||||
// Let M be the number of channels requested, and N the number of channels present,
|
||||
// and Cn be the nth channel; let stereo L be the sum of all L and center channels,
|
||||
// and stereo R be the sum of all R and center channels (channel assignment from the
|
||||
// vorbis spec).
|
||||
// M N output
|
||||
// 1 k sum(Ck) for all k
|
||||
// 2 * stereo L, stereo R
|
||||
// k l k > l, the first l channels, then 0s
|
||||
// k l k <= l, the first k channels
|
||||
// Note that this is not _good_ surround etc. mixing at all! It's just so
|
||||
// you get something useful.
|
||||
|
||||
extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); |
||||
extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); |
||||
// gets num_samples samples, not necessarily on a frame boundary--this requires
|
||||
// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES.
|
||||
// Returns the number of samples stored per channel; it may be less than requested
|
||||
// at the end of the file. If there are no more samples in the file, returns 0.
|
||||
|
||||
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION |
||||
extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); |
||||
extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); |
||||
#endif |
||||
// gets num_samples samples, not necessarily on a frame boundary--this requires
|
||||
// buffering so you have to supply the buffers. Applies the coercion rules above
|
||||
// to produce 'channels' channels. Returns the number of samples stored per channel;
|
||||
// it may be less than requested at the end of the file. If there are no more
|
||||
// samples in the file, returns 0.
|
||||
|
||||
#endif |
||||
|
||||
//////// ERROR CODES
|
||||
|
||||
enum STBVorbisError |
||||
{ |
||||
VORBIS__no_error, |
||||
|
||||
VORBIS_need_more_data=1, // not a real error
|
||||
|
||||
VORBIS_invalid_api_mixing, // can't mix API modes
|
||||
VORBIS_outofmem, // not enough memory
|
||||
VORBIS_feature_not_supported, // uses floor 0
|
||||
VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small
|
||||
VORBIS_file_open_failure, // fopen() failed
|
||||
VORBIS_seek_without_length, // can't seek in unknown-length file
|
||||
|
||||
VORBIS_unexpected_eof=10, // file is truncated?
|
||||
VORBIS_seek_invalid, // seek past EOF
|
||||
|
||||
// decoding errors (corrupt/invalid stream) -- you probably
|
||||
// don't care about the exact details of these
|
||||
|
||||
// vorbis errors:
|
||||
VORBIS_invalid_setup=20, |
||||
VORBIS_invalid_stream, |
||||
|
||||
// ogg errors:
|
||||
VORBIS_missing_capture_pattern=30, |
||||
VORBIS_invalid_stream_structure_version, |
||||
VORBIS_continued_packet_flag_invalid, |
||||
VORBIS_incorrect_stream_serial_number, |
||||
VORBIS_invalid_first_page, |
||||
VORBIS_bad_packet_type, |
||||
VORBIS_cant_find_last_page, |
||||
VORBIS_seek_failed, |
||||
VORBIS_ogg_skeleton_not_supported |
||||
}; |
||||
|
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H
|
||||
//
|
||||
// HEADER ENDS HERE
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,80 @@ |
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||
* |
||||
* SPDX-License-Identifier: GPL-3.0-only |
||||
*/ |
||||
|
||||
#include "foxenflac.hpp" |
||||
#include <stdint.h> |
||||
|
||||
#include <cstdlib> |
||||
|
||||
#include "esp_log.h" |
||||
#include "foxen/flac.h" |
||||
|
||||
namespace codecs { |
||||
|
||||
FoxenFlacDecoder::FoxenFlacDecoder() |
||||
: flac_(FX_FLAC_ALLOC(FLAC_MAX_BLOCK_SIZE, 2)) {} |
||||
|
||||
FoxenFlacDecoder::~FoxenFlacDecoder() { |
||||
free(flac_); |
||||
} |
||||
|
||||
auto FoxenFlacDecoder::BeginStream(const cpp::span<const std::byte> input) |
||||
-> Result<OutputFormat> { |
||||
uint32_t bytes_used = input.size_bytes(); |
||||
fx_flac_state_t state = |
||||
fx_flac_process(flac_, reinterpret_cast<const uint8_t*>(input.data()), |
||||
&bytes_used, NULL, NULL); |
||||
if (state != FLAC_END_OF_METADATA) { |
||||
return {bytes_used, cpp::fail(Error::kMalformedData)}; |
||||
} |
||||
|
||||
int64_t channels = fx_flac_get_streaminfo(flac_, FLAC_KEY_N_CHANNELS); |
||||
int64_t fs = fx_flac_get_streaminfo(flac_, FLAC_KEY_SAMPLE_RATE); |
||||
if (channels == FLAC_INVALID_METADATA_KEY || |
||||
fs == FLAC_INVALID_METADATA_KEY) { |
||||
return {bytes_used, cpp::fail(Error::kMalformedData)}; |
||||
} |
||||
|
||||
return {bytes_used, |
||||
OutputFormat{ |
||||
.num_channels = static_cast<uint8_t>(channels), |
||||
.bits_per_sample = 32, // libfoxenflac output is fixed-size.
|
||||
.sample_rate_hz = static_cast<uint32_t>(fs), |
||||
}}; |
||||
} |
||||
|
||||
auto FoxenFlacDecoder::ContinueStream(cpp::span<const std::byte> input, |
||||
cpp::span<std::byte> output) |
||||
-> Result<OutputInfo> { |
||||
cpp::span<int32_t> output_as_samples{ |
||||
reinterpret_cast<int32_t*>(output.data()), output.size_bytes() / 4}; |
||||
uint32_t bytes_read = input.size_bytes(); |
||||
uint32_t samples_written = output_as_samples.size(); |
||||
|
||||
fx_flac_state_t state = |
||||
fx_flac_process(flac_, reinterpret_cast<const uint8_t*>(input.data()), |
||||
&bytes_read, output_as_samples.data(), &samples_written); |
||||
if (state == FLAC_ERR) { |
||||
return {bytes_read, cpp::fail(Error::kMalformedData)}; |
||||
} |
||||
|
||||
if (samples_written > 0) { |
||||
return {bytes_read, |
||||
OutputInfo{.bytes_written = samples_written * 4, |
||||
.is_finished_writing = state == FLAC_END_OF_FRAME}}; |
||||
} |
||||
|
||||
// No error, but no samples written. We must be out of data.
|
||||
return {bytes_read, cpp::fail(Error::kOutOfInput)}; |
||||
} |
||||
|
||||
auto FoxenFlacDecoder::SeekStream(cpp::span<const std::byte> input, |
||||
std::size_t target_sample) -> Result<void> { |
||||
// TODO(jacqueline): Implement me.
|
||||
return {0, {}}; |
||||
} |
||||
|
||||
} // namespace codecs
|
@ -0,0 +1,38 @@ |
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||
* |
||||
* SPDX-License-Identifier: GPL-3.0-only |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <cstddef> |
||||
#include <cstdint> |
||||
#include <memory> |
||||
#include <optional> |
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
#include "foxen/flac.h" |
||||
#include "span.hpp" |
||||
|
||||
#include "codec.hpp" |
||||
|
||||
namespace codecs { |
||||
|
||||
class FoxenFlacDecoder : public ICodec { |
||||
public: |
||||
FoxenFlacDecoder(); |
||||
~FoxenFlacDecoder(); |
||||
|
||||
auto BeginStream(cpp::span<const std::byte>) -> Result<OutputFormat> override; |
||||
auto ContinueStream(cpp::span<const std::byte>, cpp::span<std::byte>) |
||||
-> Result<OutputInfo> override; |
||||
auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample) |
||||
-> Result<void> override; |
||||
|
||||
private: |
||||
fx_flac_t* flac_; |
||||
}; |
||||
|
||||
} // namespace codecs
|
@ -0,0 +1,42 @@ |
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||
* |
||||
* SPDX-License-Identifier: GPL-3.0-only |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <cstddef> |
||||
#include <cstdint> |
||||
#include <memory> |
||||
#include <optional> |
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
#include "stb_vorbis.h" |
||||
|
||||
#include "codec.hpp" |
||||
|
||||
namespace codecs { |
||||
|
||||
class StbVorbisDecoder : public ICodec { |
||||
public: |
||||
StbVorbisDecoder(); |
||||
~StbVorbisDecoder(); |
||||
|
||||
auto BeginStream(cpp::span<const std::byte>) -> Result<OutputFormat> override; |
||||
auto ContinueStream(cpp::span<const std::byte>, cpp::span<std::byte>) |
||||
-> Result<OutputInfo> override; |
||||
auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample) |
||||
-> Result<void> override; |
||||
|
||||
private: |
||||
stb_vorbis* vorbis_; |
||||
|
||||
int current_sample_; |
||||
int num_channels_; |
||||
int num_samples_; |
||||
float** samples_array_; |
||||
}; |
||||
|
||||
} // namespace codecs
|
@ -0,0 +1,128 @@ |
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||
* |
||||
* SPDX-License-Identifier: GPL-3.0-only |
||||
*/ |
||||
|
||||
#include "stbvorbis.hpp" |
||||
#include <stdint.h> |
||||
|
||||
#include <cstdint> |
||||
#include <optional> |
||||
|
||||
#include "stb_vorbis.h" |
||||
|
||||
namespace codecs { |
||||
|
||||
StbVorbisDecoder::StbVorbisDecoder() |
||||
: vorbis_(nullptr), |
||||
current_sample_(-1), |
||||
num_channels_(0), |
||||
num_samples_(0), |
||||
samples_array_(NULL) {} |
||||
|
||||
StbVorbisDecoder::~StbVorbisDecoder() { |
||||
if (vorbis_ != nullptr) { |
||||
stb_vorbis_close(vorbis_); |
||||
} |
||||
} |
||||
|
||||
static uint32_t scaleToBits(float sample, uint8_t bits) { |
||||
// Scale to range.
|
||||
int32_t max_val = (1 << (bits - 1)); |
||||
int32_t fixed_point = sample * max_val; |
||||
|
||||
// Clamp within bounds.
|
||||
fixed_point = std::clamp(fixed_point, -max_val, max_val); |
||||
|
||||
// Remove sign.
|
||||
return *reinterpret_cast<uint32_t*>(&fixed_point); |
||||
} |
||||
|
||||
auto StbVorbisDecoder::BeginStream(const cpp::span<const std::byte> input) |
||||
-> Result<OutputFormat> { |
||||
if (vorbis_ != nullptr) { |
||||
stb_vorbis_close(vorbis_); |
||||
vorbis_ = nullptr; |
||||
} |
||||
current_sample_ = -1; |
||||
int bytes_read = 0; |
||||
int error = 0; |
||||
vorbis_ = |
||||
stb_vorbis_open_pushdata(reinterpret_cast<const uint8_t*>(input.data()), |
||||
input.size_bytes(), &bytes_read, &error, NULL); |
||||
if (error != 0) { |
||||
return {0, cpp::fail(Error::kMalformedData)}; |
||||
} |
||||
stb_vorbis_info info = stb_vorbis_get_info(vorbis_); |
||||
return {bytes_read, |
||||
OutputFormat{.num_channels = static_cast<uint8_t>(info.channels), |
||||
.bits_per_sample = 24, |
||||
.sample_rate_hz = info.sample_rate}}; |
||||
} |
||||
|
||||
auto StbVorbisDecoder::ContinueStream(cpp::span<const std::byte> input, |
||||
cpp::span<std::byte> output) |
||||
-> Result<OutputInfo> { |
||||
std::size_t bytes_used = 0; |
||||
if (current_sample_ < 0) { |
||||
num_channels_ = 0; |
||||
num_samples_ = 0; |
||||
samples_array_ = NULL; |
||||
|
||||
while (true) { |
||||
auto cropped = input.subspan(bytes_used); |
||||
std::size_t b = stb_vorbis_decode_frame_pushdata( |
||||
vorbis_, reinterpret_cast<const uint8_t*>(cropped.data()), |
||||
cropped.size_bytes(), &num_channels_, &samples_array_, &num_samples_); |
||||
if (b == 0) { |
||||
return {bytes_used, cpp::fail(Error::kOutOfInput)}; |
||||
} |
||||
bytes_used += b; |
||||
|
||||
if (num_samples_ == 0) { |
||||
// Decoder is synchronising. Decode more bytes.
|
||||
continue; |
||||
} |
||||
if (num_channels_ == 0 || samples_array_ == NULL) { |
||||
// The decoder isn't satisfying its contract.
|
||||
return {bytes_used, cpp::fail(Error::kInternalError)}; |
||||
} |
||||
current_sample_ = 0; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// We successfully decoded a frame. Time to write out the samples.
|
||||
std::size_t output_byte = 0; |
||||
while (current_sample_ < num_samples_) { |
||||
if (output_byte + (2 * num_channels_) >= output.size()) { |
||||
return {0, OutputInfo{.bytes_written = output_byte, |
||||
.is_finished_writing = false}}; |
||||
} |
||||
|
||||
for (int channel = 0; channel < num_channels_; channel++) { |
||||
float raw_sample = samples_array_[channel][current_sample_]; |
||||
|
||||
uint16_t sample_24 = scaleToBits(raw_sample, 24); |
||||
output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF); |
||||
output[output_byte++] = static_cast<std::byte>((sample_24 >> 8) & 0xFF); |
||||
output[output_byte++] = static_cast<std::byte>((sample_24)&0xFF); |
||||
// Pad to 32 bits for alignment.
|
||||
output[output_byte++] = static_cast<std::byte>(0); |
||||
} |
||||
current_sample_++; |
||||
} |
||||
|
||||
current_sample_ = -1; |
||||
return {bytes_used, OutputInfo{.bytes_written = output_byte, |
||||
.is_finished_writing = true}}; |
||||
} |
||||
|
||||
auto StbVorbisDecoder::SeekStream(cpp::span<const std::byte> input, |
||||
std::size_t target_sample) -> Result<void> { |
||||
// TODO(jacqueline): Implement me.
|
||||
return {0, {}}; |
||||
} |
||||
|
||||
} // namespace codecs
|
@ -1,166 +0,0 @@ |
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||
* |
||||
* SPDX-License-Identifier: GPL-3.0-only |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#include <optional> |
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
#include "leveldb/db.h" |
||||
#include "span.hpp" |
||||
|
||||
namespace database { |
||||
|
||||
/*
|
||||
* Uniquely describes a single song within the database. This value will be |
||||
* consistent across database updates, and should ideally (but is not guaranteed |
||||
* to) endure even across a song being removed and re-added. |
||||
* |
||||
* Four billion songs should be enough for anybody. |
||||
*/ |
||||
typedef uint32_t SongId; |
||||
|
||||
/*
|
||||
* Audio file encodings that we are aware of. Used to select an appropriate |
||||
* decoder at play time. |
||||
* |
||||
* Values of this enum are persisted in this database, so it is probably never a |
||||
* good idea to change the int representation of an existing value. |
||||
*/ |
||||
enum class Encoding { |
||||
kUnsupported = 0, |
||||
kMp3 = 1, |
||||
kWav = 2, |
||||
kOgg = 3, |
||||
kFlac = 4, |
||||
}; |
||||
|
||||
/*
|
||||
* Owning container for tag-related song metadata that was extracted from a |
||||
* file. |
||||
*/ |
||||
struct SongTags { |
||||
Encoding encoding; |
||||
std::optional<std::string> title; |
||||
|
||||
// TODO(jacqueline): It would be nice to use shared_ptr's for the artist and
|
||||
// album, since there's likely a fair number of duplicates for each
|
||||
// (especially the former).
|
||||
|
||||
std::optional<std::string> artist; |
||||
std::optional<std::string> album; |
||||
|
||||
std::optional<int> channels; |
||||
std::optional<int> sample_rate; |
||||
std::optional<int> bits_per_sample; |
||||
|
||||
/*
|
||||
* Returns a hash of the 'identifying' tags of this song. That is, a hash that |
||||
* can be used to determine if one song is likely the same as another, across |
||||
* things like re-encoding, re-mastering, or moving the underlying file. |
||||
*/ |
||||
auto Hash() const -> uint64_t; |
||||
|
||||
bool operator==(const SongTags&) const = default; |
||||
}; |
||||
|
||||
/*
|
||||
* Immutable owning container for all of the metadata we store for a particular |
||||
* song. This includes two main kinds of metadata: |
||||
* 1. static(ish) attributes, such as the id, path on disk, hash of the tags |
||||
* 2. dynamic attributes, such as the number of times this song has been |
||||
* played. |
||||
* |
||||
* Because a SongData is immutable, it is thread safe but will not reflect any |
||||
* changes to the dynamic attributes that may happen after it was obtained. |
||||
* |
||||
* Songs may be 'tombstoned'; this indicates that the song is no longer present |
||||
* at its previous location on disk, and we do not have any existing files with |
||||
* a matching tags_hash. When this is the case, we ignore this SongData for most |
||||
* purposes. We keep the entry in our database so that we can properly restore |
||||
* dynamic attributes (such as play count) if the song later re-appears on disk. |
||||
*/ |
||||
class SongData { |
||||
private: |
||||
const SongId id_; |
||||
const std::string filepath_; |
||||
const uint64_t tags_hash_; |
||||
const uint32_t play_count_; |
||||
const bool is_tombstoned_; |
||||
|
||||
public: |
||||
/* Constructor used when adding new songs to the database. */ |
||||
SongData(SongId id, const std::string& path, uint64_t hash) |
||||
: id_(id), |
||||
filepath_(path), |
||||
tags_hash_(hash), |
||||
play_count_(0), |
||||
is_tombstoned_(false) {} |
||||
|
||||
SongData(SongId id, |
||||
const std::string& path, |
||||
uint64_t hash, |
||||
uint32_t play_count, |
||||
bool is_tombstoned) |
||||
: id_(id), |
||||
filepath_(path), |
||||
tags_hash_(hash), |
||||
play_count_(play_count), |
||||
is_tombstoned_(is_tombstoned) {} |
||||
|
||||
auto id() const -> SongId { return id_; } |
||||
auto filepath() const -> std::string { return filepath_; } |
||||
auto play_count() const -> uint32_t { return play_count_; } |
||||
auto tags_hash() const -> uint64_t { return tags_hash_; } |
||||
auto is_tombstoned() const -> bool { return is_tombstoned_; } |
||||
|
||||
auto UpdateHash(uint64_t new_hash) const -> SongData; |
||||
|
||||
/*
|
||||
* Marks this song data as a 'tombstone'. Tombstoned songs are not playable, |
||||
* and should not generally be shown to users. |
||||
*/ |
||||
auto Entomb() const -> SongData; |
||||
|
||||
/*
|
||||
* Clears the tombstone bit of this song, and updates the path to reflect its |
||||
* new location. |
||||
*/ |
||||
auto Exhume(const std::string& new_path) const -> SongData; |
||||
|
||||
bool operator==(const SongData&) const = default; |
||||
}; |
||||
|
||||
/*
|
||||
* Immutable and owning combination of a song's tags and metadata. |
||||
* |
||||
* Note that instances of this class may have a fairly large memory impact, due |
||||
* to the large number of strings they own. Prefer to query the database again |
||||
* (which has its own caching layer), rather than retaining Song instances for a |
||||
* long time. |
||||
*/ |
||||
class Song { |
||||
public: |
||||
Song(const SongData& data, const SongTags& tags) : data_(data), tags_(tags) {} |
||||
Song(const Song& other) = default; |
||||
|
||||
auto data() const -> const SongData& { return data_; } |
||||
auto tags() const -> const SongTags& { return tags_; } |
||||
|
||||
bool operator==(const Song&) const = default; |
||||
Song operator=(const Song& other) const { return Song(other); } |
||||
|
||||
private: |
||||
const SongData data_; |
||||
const SongTags tags_; |
||||
}; |
||||
|
||||
void swap(Song& first, Song& second); |
||||
|
||||
} // namespace database
|
@ -0,0 +1,169 @@ |
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||
* |
||||
* SPDX-License-Identifier: GPL-3.0-only |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#include <optional> |
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
#include "leveldb/db.h" |
||||
#include "span.hpp" |
||||
|
||||
namespace database { |
||||
|
||||
/*
|
||||
* Uniquely describes a single track within the database. This value will be |
||||
* consistent across database updates, and should ideally (but is not guaranteed |
||||
* to) endure even across a track being removed and re-added. |
||||
* |
||||
* Four billion tracks should be enough for anybody. |
||||
*/ |
||||
typedef uint32_t TrackId; |
||||
|
||||
/*
|
||||
* Audio file encodings that we are aware of. Used to select an appropriate |
||||
* decoder at play time. |
||||
* |
||||
* Values of this enum are persisted in this database, so it is probably never a |
||||
* good idea to change the int representation of an existing value. |
||||
*/ |
||||
enum class Encoding { |
||||
kUnsupported = 0, |
||||
kMp3 = 1, |
||||
kWav = 2, |
||||
kOgg = 3, |
||||
kFlac = 4, |
||||
}; |
||||
|
||||
/*
|
||||
* Owning container for tag-related track metadata that was extracted from a |
||||
* file. |
||||
*/ |
||||
struct TrackTags { |
||||
Encoding encoding; |
||||
std::optional<std::string> title; |
||||
|
||||
// TODO(jacqueline): It would be nice to use shared_ptr's for the artist and
|
||||
// album, since there's likely a fair number of duplicates for each
|
||||
// (especially the former).
|
||||
|
||||
std::optional<std::string> artist; |
||||
std::optional<std::string> album; |
||||
|
||||
std::optional<int> channels; |
||||
std::optional<int> sample_rate; |
||||
std::optional<int> bits_per_sample; |
||||
|
||||
/*
|
||||
* Returns a hash of the 'identifying' tags of this track. That is, a hash |
||||
* that can be used to determine if one track is likely the same as another, |
||||
* across things like re-encoding, re-mastering, or moving the underlying |
||||
* file. |
||||
*/ |
||||
auto Hash() const -> uint64_t; |
||||
|
||||
bool operator==(const TrackTags&) const = default; |
||||
}; |
||||
|
||||
/*
|
||||
* Immutable owning container for all of the metadata we store for a particular |
||||
* track. This includes two main kinds of metadata: |
||||
* 1. static(ish) attributes, such as the id, path on disk, hash of the tags |
||||
* 2. dynamic attributes, such as the number of times this track has been |
||||
* played. |
||||
* |
||||
* Because a TrackData is immutable, it is thread safe but will not reflect any |
||||
* changes to the dynamic attributes that may happen after it was obtained. |
||||
* |
||||
* Tracks may be 'tombstoned'; this indicates that the track is no longer |
||||
* present at its previous location on disk, and we do not have any existing |
||||
* files with a matching tags_hash. When this is the case, we ignore this |
||||
* TrackData for most purposes. We keep the entry in our database so that we can |
||||
* properly restore dynamic attributes (such as play count) if the track later |
||||
* re-appears on disk. |
||||
*/ |
||||
class TrackData { |
||||
private: |
||||
const TrackId id_; |
||||
const std::string filepath_; |
||||
const uint64_t tags_hash_; |
||||
const uint32_t play_count_; |
||||
const bool is_tombstoned_; |
||||
|
||||
public: |
||||
/* Constructor used when adding new tracks to the database. */ |
||||
TrackData(TrackId id, const std::string& path, uint64_t hash) |
||||
: id_(id), |
||||
filepath_(path), |
||||
tags_hash_(hash), |
||||
play_count_(0), |
||||
is_tombstoned_(false) {} |
||||
|
||||
TrackData(TrackId id, |
||||
const std::string& path, |
||||
uint64_t hash, |
||||
uint32_t play_count, |
||||
bool is_tombstoned) |
||||
: id_(id), |
||||
filepath_(path), |
||||
tags_hash_(hash), |
||||
play_count_(play_count), |
||||
is_tombstoned_(is_tombstoned) {} |
||||
|
||||
auto id() const -> TrackId { return id_; } |
||||
auto filepath() const -> std::string { return filepath_; } |
||||
auto play_count() const -> uint32_t { return play_count_; } |
||||
auto tags_hash() const -> uint64_t { return tags_hash_; } |
||||
auto is_tombstoned() const -> bool { return is_tombstoned_; } |
||||
|
||||
auto UpdateHash(uint64_t new_hash) const -> TrackData; |
||||
|
||||
/*
|
||||
* Marks this track data as a 'tombstone'. Tombstoned tracks are not playable, |
||||
* and should not generally be shown to users. |
||||
*/ |
||||
auto Entomb() const -> TrackData; |
||||
|
||||
/*
|
||||
* Clears the tombstone bit of this track, and updates the path to reflect its |
||||
* new location. |
||||
*/ |
||||
auto Exhume(const std::string& new_path) const -> TrackData; |
||||
|
||||
bool operator==(const TrackData&) const = default; |
||||
}; |
||||
|
||||
/*
|
||||
* Immutable and owning combination of a track's tags and metadata. |
||||
* |
||||
* Note that instances of this class may have a fairly large memory impact, due |
||||
* to the large number of strings they own. Prefer to query the database again |
||||
* (which has its own caching layer), rather than retaining Track instances for |
||||
* a long time. |
||||
*/ |
||||
class Track { |
||||
public: |
||||
Track(const TrackData& data, const TrackTags& tags) |
||||
: data_(data), tags_(tags) {} |
||||
Track(const Track& other) = default; |
||||
|
||||
auto data() const -> const TrackData& { return data_; } |
||||
auto tags() const -> const TrackTags& { return tags_; } |
||||
|
||||
bool operator==(const Track&) const = default; |
||||
Track operator=(const Track& other) const { return Track(other); } |
||||
|
||||
private: |
||||
const TrackData data_; |
||||
const TrackTags tags_; |
||||
}; |
||||
|
||||
void swap(Track& first, Track& second); |
||||
|
||||
} // namespace database
|
Loading…
Reference in new issue