rockbox/apps/plugins/h264_player/video_audio.c
Aidan MacDonald d27af08ff6 hw_h264: remove redundant ifdef guards
Change-Id: I5fa1abc1226ec5a35888713da83467a71a3c4caf
2026-09-23 16:22:18 +01:00

572 lines
16 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2025-2026 David Cormier
*
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
/*
* AAC audio service for the native S5L8702 H.264 player.
*
* The decoder itself remains Rockbox's normal dynamically-loaded aac.codec.
* That is important on iPod 6G: FAAD's hot code and working state are an IRAM
* overlay and cannot be linked into the permanent core without overflowing
* IRAM. This file borrows the existing codec thread, supplies file-buffer
* callbacks for the MP4, and sends decoded PCM to video_pcm.
*/
#include "plugin.h"
#include <stddef.h>
#include "codec_thread.h"
#include "codecs.h"
#include "file.h"
#include "kernel.h"
#include "metadata.h"
#include "mp4_demux.h"
#include "video_audio.h"
#include "video_pcm.h"
#include <stdint.h>
#include <string.h>
#define AUDIO_FILE_BUFFER_SIZE 8192
#define AUDIO_PCM_FRAMES 1024
#define AUDIO_START_BUFFER_MS 1000
/* libm4a's sample_offset_t stores a uint32_t sample and file offset. Keep a
* normal CODEC_SIZE of headroom after the complete Apple chunk map. */
#define AUDIO_CHUNK_LOOKUP_ENTRY_SIZE (2u * sizeof(uint32_t))
static struct codec_api video_ci;
static struct mp3entry video_id3;
static int audio_fd = -1;
static off_t audio_file_size;
static uint8_t audio_file_buffer[AUDIO_FILE_BUFFER_SIZE];
static off_t audio_file_buffer_offset;
static size_t audio_file_buffer_length;
static int16_t audio_pcm[AUDIO_PCM_FRAMES * 2];
static void *audio_codec_workspace;
static size_t audio_codec_workspace_size;
static uint32_t audio_sample_rate;
static int audio_sample_depth;
static int audio_stereo_mode;
static volatile long audio_action;
static volatile uint32_t audio_seek_target_ms;
static volatile bool audio_paused;
static volatile bool audio_is_ready;
static volatile bool audio_is_playing;
static volatile bool audio_has_error;
static bool audio_initialized;
size_t video_audio_workspace_size(const struct mp4v_demux_res *demux)
{
size_t table_size;
if (demux == NULL || demux->audio_num_samples == 0 ||
demux->audio_num_stco == 0 ||
demux->audio_num_stco >
(SIZE_MAX - CODEC_SIZE) / AUDIO_CHUNK_LOOKUP_ENTRY_SIZE)
return 0;
table_size = (size_t)demux->audio_num_stco *
AUDIO_CHUNK_LOOKUP_ENTRY_SIZE;
return CODEC_SIZE + table_size;
}
static int16_t audio_clip16(int32_t value)
{
if (value > 32767)
return 32767;
if (value < -32768)
return -32768;
return (int16_t)value;
}
static void *audio_codec_get_buffer(size_t *size)
{
*size = audio_codec_workspace_size;
return audio_codec_workspace;
}
static bool audio_refill(off_t position)
{
ssize_t count;
if (position < 0 || position > audio_file_size ||
rb->lseek(audio_fd, position, SEEK_SET) < 0)
return false;
count = rb->read(audio_fd, audio_file_buffer, sizeof(audio_file_buffer));
if (count < 0)
return false;
audio_file_buffer_offset = position;
audio_file_buffer_length = (size_t)count;
return true;
}
static size_t audio_read_filebuf(void *destination, size_t size)
{
size_t done = 0;
while (done < size && video_ci.curpos < audio_file_size)
{
size_t in_buffer;
size_t chunk;
if (video_ci.curpos < audio_file_buffer_offset ||
video_ci.curpos >= audio_file_buffer_offset +
(off_t)audio_file_buffer_length)
{
if (!audio_refill(video_ci.curpos))
break;
}
in_buffer = (size_t)(video_ci.curpos - audio_file_buffer_offset);
chunk = audio_file_buffer_length - in_buffer;
if (chunk > size - done)
chunk = size - done;
if (chunk == 0)
break;
rb->memcpy((uint8_t *)destination + done,
audio_file_buffer + in_buffer, chunk);
video_ci.curpos += chunk;
done += chunk;
}
return done;
}
static void *audio_request_buffer(size_t *real_size, size_t requested)
{
size_t available;
size_t in_buffer;
size_t needed;
off_t remaining;
if (video_ci.curpos >= audio_file_size)
{
*real_size = 0;
return audio_file_buffer;
}
remaining = audio_file_size - video_ci.curpos;
needed = requested;
if ((off_t)needed > remaining)
needed = (size_t)remaining;
if (video_ci.curpos < audio_file_buffer_offset ||
video_ci.curpos >= audio_file_buffer_offset +
(off_t)audio_file_buffer_length)
{
if (!audio_refill(video_ci.curpos))
{
*real_size = 0;
return audio_file_buffer;
}
}
in_buffer = (size_t)(video_ci.curpos - audio_file_buffer_offset);
available = audio_file_buffer_length - in_buffer;
/* codec_api.request_buffer promises the requested span whenever that
* many file bytes remain. Do not return the short tail of our 8 KiB
* cache: FAAD treats a truncated AAC frame as a fatal decode error. */
if (available < needed)
{
if (!audio_refill(video_ci.curpos))
{
*real_size = 0;
return audio_file_buffer;
}
in_buffer = 0;
available = audio_file_buffer_length;
}
if (available > needed)
available = needed;
*real_size = available;
return audio_file_buffer + in_buffer;
}
static void audio_advance_buffer(size_t amount)
{
off_t remaining = audio_file_size - video_ci.curpos;
if ((off_t)amount > remaining)
amount = remaining > 0 ? (size_t)remaining : 0;
video_ci.curpos += amount;
video_id3.offset = video_ci.curpos;
}
static bool audio_seek_buffer(size_t position)
{
if ((off_t)position > audio_file_size)
return false;
video_ci.curpos = position;
video_id3.offset = position;
return true;
}
static void audio_seek_complete(void)
{
}
static void audio_set_elapsed(unsigned long elapsed)
{
video_id3.elapsed = elapsed;
}
static void audio_set_offset(size_t offset)
{
video_id3.offset = offset;
}
static void audio_configure(int setting, intptr_t value)
{
switch (setting)
{
case DSP_SET_FREQUENCY:
if (value > 0)
audio_sample_rate = (uint32_t)value;
break;
case DSP_SET_SAMPLE_DEPTH:
audio_sample_depth = (int)value;
break;
case DSP_SET_STEREO_MODE:
audio_stereo_mode = (int)value;
break;
default:
break;
}
}
static long audio_get_command(intptr_t *parameter)
{
long action;
while (audio_paused && audio_action == CODEC_ACTION_NULL)
rb->sleep(1);
rb->yield();
action = audio_action;
if (action == CODEC_ACTION_SEEK_TIME)
{
if (parameter != NULL)
*parameter = (intptr_t)audio_seek_target_ms;
/* A seek is a one-shot command. HALT deliberately remains latched
* until codec teardown so the producer cannot resume behind us. */
audio_action = CODEC_ACTION_NULL;
}
return action;
}
static bool audio_loop_track(void)
{
return false;
}
static void audio_strip_filesize(off_t size)
{
video_ci.filesize = size;
}
static int32_t audio_sample_value(const void *channel, int index)
{
if (audio_sample_depth <= 16)
return ((const int16_t *)channel)[index];
{
int shift = audio_sample_depth + 1 - 16;
int32_t value = ((const int32_t *)channel)[index];
int32_t bias = shift > 0 ? (int32_t)1 << (shift - 1) : 0;
return shift > 0 ? (value + bias) >> shift : value;
}
}
static void audio_pcm_insert(const void *channel1, const void *channel2,
int count)
{
int consumed = 0;
while (consumed < count && audio_action != CODEC_ACTION_HALT)
{
int frames;
int written = 0;
int i;
while (audio_paused && audio_action != CODEC_ACTION_HALT)
rb->sleep(1);
if (audio_action == CODEC_ACTION_HALT)
break;
frames = count - consumed;
if (frames > AUDIO_PCM_FRAMES)
frames = AUDIO_PCM_FRAMES;
for (i = 0; i < frames; i++)
{
int source = consumed + i;
int32_t left;
int32_t right;
if (audio_stereo_mode == STEREO_INTERLEAVED)
{
left = audio_sample_value(channel1, source * 2);
right = audio_sample_value(channel1, source * 2 + 1);
}
else
{
left = audio_sample_value(channel1, source);
right = audio_stereo_mode == STEREO_MONO ? left :
audio_sample_value(channel2, source);
}
audio_pcm[i * 2] = audio_clip16(left);
audio_pcm[i * 2 + 1] = audio_clip16(right);
}
while (written < frames && audio_action != CODEC_ACTION_HALT)
{
int amount;
while (audio_paused && audio_action != CODEC_ACTION_HALT)
rb->sleep(1);
amount = video_pcm_write(audio_pcm + written * 2,
frames - written);
if (amount == 0)
{
rb->sleep(1);
continue;
}
written += amount;
}
consumed += frames;
if (!audio_is_ready && audio_sample_rate > 0 &&
video_pcm_buffered_samples() >=
audio_sample_rate * AUDIO_START_BUFFER_MS / 1000)
audio_is_ready = true;
}
}
static void audio_codec_api_init(void)
{
rb->memset(&video_ci, 0, sizeof(video_ci));
video_ci.dsp = rb->dsp_get_config(CODEC_IDX_AUDIO);
video_ci.codec_get_buffer = audio_codec_get_buffer;
video_ci.pcmbuf_insert = audio_pcm_insert;
video_ci.set_elapsed = audio_set_elapsed;
video_ci.read_filebuf = audio_read_filebuf;
video_ci.request_buffer = audio_request_buffer;
video_ci.advance_buffer = audio_advance_buffer;
video_ci.seek_buffer = audio_seek_buffer;
video_ci.seek_complete = audio_seek_complete;
video_ci.set_offset = audio_set_offset;
video_ci.configure = audio_configure;
video_ci.get_command = audio_get_command;
video_ci.loop_track = audio_loop_track;
video_ci.strip_filesize = audio_strip_filesize;
#if defined(ARM_NEED_DIV0)
video_ci.__div0 = rb->__div0;
#endif
video_ci.sleep = rb->sleep;
video_ci.yield = rb->yield;
#if NUM_CORES > 1
video_ci.create_thread = rb->create_thread;
video_ci.thread_thaw = rb->thread_thaw;
video_ci.thread_wait = rb->thread_wait;
video_ci.semaphore_init = rb->semaphore_init;
video_ci.semaphore_wait = rb->semaphore_wait;
video_ci.semaphore_release = rb->semaphore_release;
#endif
video_ci.commit_dcache = rb->commit_dcache;
video_ci.commit_discard_dcache = rb->commit_discard_dcache;
video_ci.commit_discard_idcache = rb->commit_discard_idcache;
video_ci.strcpy = rb->strcpy;
video_ci.strlen = rb->strlen;
video_ci.strcmp = rb->strcmp;
video_ci.strcat = rb->strcat;
video_ci.memset = rb->memset;
video_ci.memcpy = rb->memcpy;
video_ci.memmove = rb->memmove;
video_ci.memcmp = rb->memcmp;
video_ci.memchr = rb->memchr;
#if defined(DEBUG) || defined(SIMULATOR)
video_ci.debugf = rb->debugf;
#endif
#ifdef ROCKBOX_HAS_LOGF
video_ci.logf = rb->logf;
#endif
video_ci.qsort = rb->qsort;
#ifdef RB_PROFILE
video_ci.profile_thread = rb->profile_thread;
video_ci.profstop = rb->profstop;
video_ci.profile_func_enter = rb->profile_func_enter;
video_ci.profile_func_exit = rb->profile_func_exit;
#endif
#ifdef HAVE_RECORDING
video_ci.round_value_to_list32 = rb->round_value_to_list32;
#endif
video_ci.panicf = rb->panicf;
}
static void audio_codec_worker(void)
{
int status;
#ifdef HAVE_PRIORITY_SCHEDULING
/* Match mpegplayer: core DSP/AAC yields often enough that the normal
* codec priority can be starved by the video render loop. */
int old_priority = rb->thread_set_priority(rb->thread_self(),
PRIORITY_PLAYBACK - 4);
#endif
status = rb->codec_load_file("aac", &video_ci);
if (status >= 0)
status = rb->codec_run_proc();
rb->codec_close();
#ifdef HAVE_PRIORITY_SCHEDULING
rb->thread_set_priority(rb->thread_self(), old_priority);
#endif
if (status < 0 && audio_action != CODEC_ACTION_HALT)
audio_has_error = true;
audio_is_ready = true;
audio_is_playing = false;
}
int video_audio_init(const char *filepath,
const struct mp4v_demux_res *demux,
void *codec_workspace, size_t workspace_size,
void *pcm_buffer, size_t pcm_buffer_size)
{
size_t required_workspace = video_audio_workspace_size(demux);
if (audio_initialized || filepath == NULL || demux == NULL ||
codec_workspace == NULL || pcm_buffer == NULL ||
required_workspace == 0 ||
workspace_size < required_workspace ||
demux->audio_format != MAKEFOURCC('m', 'p', '4', 'a') ||
demux->audio_sample_rate == 0 || demux->audio_num_samples == 0)
return -1;
audio_fd = rb->open(filepath, O_RDONLY);
if (audio_fd < 0)
return -1;
audio_file_size = rb->ffilesize(audio_fd);
if (audio_file_size <= 0)
{
rb->close(audio_fd);
audio_fd = -1;
return -1;
}
audio_codec_workspace = codec_workspace;
audio_codec_workspace_size = workspace_size;
audio_file_buffer_offset = 0;
audio_file_buffer_length = 0;
audio_sample_rate = demux->audio_sample_rate;
audio_sample_depth = 29;
audio_stereo_mode = STEREO_NONINTERLEAVED;
audio_action = CODEC_ACTION_NULL;
audio_seek_target_ms = 0;
audio_paused = false;
audio_is_ready = false;
audio_is_playing = false;
audio_has_error = false;
rb->memset(&video_id3, 0, sizeof(video_id3));
video_id3.codectype = AFMT_MP4_AAC;
video_id3.frequency = demux->audio_sample_rate;
video_id3.lead_trim = demux->audio_lead_trim;
audio_codec_api_init();
video_ci.filesize = audio_file_size;
video_ci.curpos = 0;
video_ci.id3 = &video_id3;
video_ci.audio_hid = -1;
if (!video_pcm_init(audio_sample_rate, pcm_buffer, pcm_buffer_size))
{
rb->close(audio_fd);
audio_fd = -1;
audio_codec_workspace = NULL;
audio_codec_workspace_size = 0;
return -1;
}
audio_initialized = true;
return 0;
}
void video_audio_play(void)
{
if (!audio_initialized || audio_is_playing)
return;
audio_action = CODEC_ACTION_NULL;
audio_paused = false;
audio_is_playing = true;
rb->codec_thread_do_callback(audio_codec_worker, NULL);
}
void video_audio_pause(void)
{
audio_paused = true;
}
void video_audio_resume(void)
{
audio_paused = false;
}
void video_audio_seek(uint32_t target_ms)
{
if (!audio_initialized || !audio_is_playing)
return;
audio_is_ready = false;
audio_seek_target_ms = target_ms;
video_pcm_flush(target_ms, audio_sample_rate);
audio_action = CODEC_ACTION_SEEK_TIME;
}
void video_audio_stop(void)
{
if (!audio_initialized)
return;
audio_action = CODEC_ACTION_HALT;
audio_paused = false;
rb->codec_thread_do_callback(NULL, NULL);
video_pcm_stop();
if (audio_fd >= 0)
{
rb->close(audio_fd);
audio_fd = -1;
}
audio_codec_workspace = NULL;
audio_codec_workspace_size = 0;
audio_initialized = false;
audio_is_playing = false;
}
bool video_audio_ready(void)
{
return audio_is_ready || audio_has_error;
}
bool video_audio_is_active(void)
{
return audio_is_playing;
}
bool video_audio_failed(void)
{
return audio_has_error;
}