mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
ipod6g: add hardware H.264 video playback
Add S5L8702 VPU-B initialization, reset, bitstream input, frame output, and cache maintenance to the iPod 6G target. Advertise it with HAVE_HW_H264 and append a capability-gated decoder interface to the plugin API. Keep MP4 parsing, AAC decode and mixer output, A/V synchronization, playback controls, resume state, and presentation in the multi-file h264_player plugin. The target layer exposes decoder operations only. Accept non-fragmented MP4/M4V containing Constrained Baseline H.264 through level 3.0, up to 640x480 at 30 fps, with optional AAC-LC audio. Validate codec configuration and all sample-table relationships before activating the hardware. Read MP4 tables in bulk, boost the CPU while preparing them, and report staged loading progress so long movies do not appear to hang during startup. Register the viewer and document its format limits and controls. The libm4a compatibility fixes needed by video-first containers remain in the preceding standalone change. Tested on an iPod Classic 6G through an isolated Rolo nightly runtime: H.264/AAC playback and M4V startup succeeded. Normal and isolated-runtime iPod 6G builds also complete, and git diff --check is clean. Change-Id: I1e96c65c7d0b4231a94f602f8052f1b26d6e4a80
This commit is contained in:
parent
57a91121f6
commit
e0136bc7f4
28 changed files with 5270 additions and 1 deletions
|
|
@ -49,6 +49,9 @@
|
||||||
#include "language.h"
|
#include "language.h"
|
||||||
#include "statusbar-skinned.h"
|
#include "statusbar-skinned.h"
|
||||||
#include "panic.h"
|
#include "panic.h"
|
||||||
|
#ifdef HAVE_HW_H264
|
||||||
|
#include "hw_h264.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if CONFIG_CHARGING
|
#if CONFIG_CHARGING
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
|
|
@ -880,6 +883,9 @@ static const struct plugin_api rockbox_api = {
|
||||||
gesture_flick_get_in_vp,
|
gesture_flick_get_in_vp,
|
||||||
gesture_flick_get,
|
gesture_flick_get,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_HW_H264
|
||||||
|
&target_hw_h264_api,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static int plugin_buffer_handle;
|
static int plugin_buffer_handle;
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ int plugin_open(const char *plugin, const char *parameter);
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
#ifdef HAVE_HW_H264
|
||||||
|
#include "hw_h264.h"
|
||||||
|
#endif
|
||||||
#include "voice_thread.h"
|
#include "voice_thread.h"
|
||||||
#include "root_menu.h"
|
#include "root_menu.h"
|
||||||
#include "talk.h"
|
#include "talk.h"
|
||||||
|
|
@ -179,7 +182,7 @@ int plugin_open(const char *plugin, const char *parameter);
|
||||||
* when this happens please take the opportunity to sort in
|
* when this happens please take the opportunity to sort in
|
||||||
* any new functions "waiting" at the end of the list.
|
* any new functions "waiting" at the end of the list.
|
||||||
*/
|
*/
|
||||||
#define PLUGIN_API_VERSION 284
|
#define PLUGIN_API_VERSION 285
|
||||||
|
|
||||||
/* 239 Marks the removal of ARCHOS HWCODEC and CHARCELL */
|
/* 239 Marks the removal of ARCHOS HWCODEC and CHARCELL */
|
||||||
|
|
||||||
|
|
@ -1046,6 +1049,9 @@ struct plugin_api {
|
||||||
const struct viewport *vp);
|
const struct viewport *vp);
|
||||||
int (*gesture_flick_get)(const struct gesture_event *gevt);
|
int (*gesture_flick_get)(const struct gesture_event *gevt);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_HW_H264
|
||||||
|
const struct hw_h264_api *hw_h264;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/* plugin header */
|
/* plugin header */
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,10 @@ pdbox
|
||||||
mpegplayer
|
mpegplayer
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_HW_H264
|
||||||
|
h264_player
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Lua needs at least 160 KB to work in */
|
/* Lua needs at least 160 KB to work in */
|
||||||
#if PLUGIN_BUFFER_SIZE >= 0x80000
|
#if PLUGIN_BUFFER_SIZE >= 0x80000
|
||||||
lua
|
lua
|
||||||
|
|
|
||||||
5
apps/plugins/h264_player/SOURCES
Normal file
5
apps/plugins/h264_player/SOURCES
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
mp4_demux.c
|
||||||
|
video_audio.c
|
||||||
|
video_pcm.c
|
||||||
|
video_playback.c
|
||||||
|
h264_player.c
|
||||||
51
apps/plugins/h264_player/h264_player.c
Normal file
51
apps/plugins/h264_player/h264_player.c
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
*
|
||||||
|
* Copyright (C) 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.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "plugin.h"
|
||||||
|
#include "video_playback.h"
|
||||||
|
|
||||||
|
enum plugin_status plugin_start(const void *parameter)
|
||||||
|
{
|
||||||
|
const char *path = parameter;
|
||||||
|
size_t buffer_size = 0;
|
||||||
|
void *buffer;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
if (path == NULL || path[0] == '\0')
|
||||||
|
{
|
||||||
|
rb->splash(HZ * 2, "No video file");
|
||||||
|
return PLUGIN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = rb->plugin_get_audio_buffer(&buffer_size);
|
||||||
|
if (buffer == NULL || buffer_size == 0)
|
||||||
|
{
|
||||||
|
rb->splash(HZ * 2, "Not enough video memory");
|
||||||
|
return PLUGIN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = video_h264_play(path, buffer, buffer_size);
|
||||||
|
rb->button_clear_queue();
|
||||||
|
rb->plugin_release_audio_buffer();
|
||||||
|
|
||||||
|
if (result == 2)
|
||||||
|
return PLUGIN_USB_CONNECTED;
|
||||||
|
return result < 0 ? PLUGIN_ERROR : PLUGIN_OK;
|
||||||
|
}
|
||||||
26
apps/plugins/h264_player/h264_player.make
Normal file
26
apps/plugins/h264_player/h264_player.make
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# __________ __ ___.
|
||||||
|
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
# \/ \/ \/ \/ \/
|
||||||
|
#
|
||||||
|
|
||||||
|
H264PLAYERSRCDIR := $(APPSDIR)/plugins/h264_player
|
||||||
|
H264PLAYERBUILDDIR := $(BUILDDIR)/apps/plugins/h264_player
|
||||||
|
|
||||||
|
ROCKS += $(H264PLAYERBUILDDIR)/h264_player.rock
|
||||||
|
|
||||||
|
H264PLAYER_SRC := $(call preprocess, $(H264PLAYERSRCDIR)/SOURCES)
|
||||||
|
H264PLAYER_OBJ := $(call c2obj, $(H264PLAYER_SRC))
|
||||||
|
|
||||||
|
# add source files to OTHER_SRC to get automatic dependencies
|
||||||
|
OTHER_SRC += $(H264PLAYER_SRC)
|
||||||
|
|
||||||
|
$(H264PLAYERBUILDDIR)/h264_player.rock: $(H264PLAYER_OBJ)
|
||||||
|
|
||||||
|
$(H264PLAYERBUILDDIR)/%.o: $(H264PLAYERSRCDIR)/%.c \
|
||||||
|
$(H264PLAYERSRCDIR)/h264_player.make
|
||||||
|
$(SILENT)mkdir -p $(dir $@)
|
||||||
|
$(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) \
|
||||||
|
$(PLUGINFLAGS) -c $< -o $@
|
||||||
1548
apps/plugins/h264_player/mp4_demux.c
Normal file
1548
apps/plugins/h264_player/mp4_demux.c
Normal file
File diff suppressed because it is too large
Load diff
150
apps/plugins/h264_player/mp4_demux.h
Normal file
150
apps/plugins/h264_player/mp4_demux.h
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025-2026 David Cormier
|
||||||
|
*
|
||||||
|
* H.264 player MP4 demuxer - extracts video track data from MP4/M4V files.
|
||||||
|
* Forked from libm4a/demux.c, adapted for fd-based I/O and video tracks.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
#ifndef MP4_DEMUX_H
|
||||||
|
#define MP4_DEMUX_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/* Max SPS/PPS codec data from avcC box */
|
||||||
|
#define MP4V_MAX_CODECDATA 512
|
||||||
|
|
||||||
|
#define MP4V_MAX_STTS 4096
|
||||||
|
|
||||||
|
#ifndef MAKEFOURCC
|
||||||
|
#define MAKEFOURCC(ch0, ch1, ch2, ch3) ( \
|
||||||
|
( (int32_t)(char)(ch0) << 24 ) | \
|
||||||
|
( (int32_t)(char)(ch1) << 16 ) | \
|
||||||
|
( (int32_t)(char)(ch2) << 8 ) | \
|
||||||
|
( (int32_t)(char)(ch3) ) )
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct mp4v_stts_entry {
|
||||||
|
uint32_t sample_count;
|
||||||
|
uint32_t sample_delta;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mp4v_stsc_entry {
|
||||||
|
uint32_t first_chunk;
|
||||||
|
uint32_t samples_per_chunk;
|
||||||
|
uint32_t sample_desc_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mp4v_demux_res {
|
||||||
|
/* Video format */
|
||||||
|
uint32_t format; /* fourcc: 'avc1' etc. */
|
||||||
|
uint16_t width; /* coded width from avc1 sample entry */
|
||||||
|
uint16_t height; /* coded height from avc1 sample entry */
|
||||||
|
uint16_t display_width; /* display width from tkhd (PAR-adjusted) */
|
||||||
|
uint16_t display_height; /* display height from tkhd */
|
||||||
|
uint32_t timescale; /* from mdhd */
|
||||||
|
|
||||||
|
/* avcC decoder config (SPS + PPS) */
|
||||||
|
uint8_t avc_profile;
|
||||||
|
uint8_t avc_level;
|
||||||
|
uint8_t nalu_len_size; /* length field size (1-4 bytes, typically 4) */
|
||||||
|
uint8_t num_sps;
|
||||||
|
uint8_t num_pps;
|
||||||
|
uint32_t codecdata_len;
|
||||||
|
uint8_t codecdata[MP4V_MAX_CODECDATA]; /* raw avcC data */
|
||||||
|
|
||||||
|
/* Sample sizes (stsz) */
|
||||||
|
uint32_t num_samples;
|
||||||
|
uint32_t *sample_sizes; /* caller-provided buffer */
|
||||||
|
uint32_t sample_sizes_cap;
|
||||||
|
|
||||||
|
/* Time-to-sample (stts) */
|
||||||
|
uint32_t num_stts;
|
||||||
|
struct mp4v_stts_entry stts[MP4V_MAX_STTS];
|
||||||
|
|
||||||
|
/* Sample-to-chunk (stsc) */
|
||||||
|
uint32_t num_stsc;
|
||||||
|
struct mp4v_stsc_entry *stsc; /* caller-provided buffer */
|
||||||
|
uint32_t stsc_cap;
|
||||||
|
|
||||||
|
/* Chunk offsets (stco) */
|
||||||
|
uint32_t num_stco;
|
||||||
|
uint32_t *chunk_offsets; /* caller-provided buffer */
|
||||||
|
uint32_t chunk_offsets_cap;
|
||||||
|
|
||||||
|
/* Sync samples / keyframes (stss) */
|
||||||
|
uint32_t num_stss;
|
||||||
|
uint32_t *sync_samples; /* caller-provided buffer */
|
||||||
|
uint32_t sync_samples_cap;
|
||||||
|
|
||||||
|
/* File and mdat location */
|
||||||
|
uint32_t file_size;
|
||||||
|
uint32_t mdat_offset;
|
||||||
|
uint32_t mdat_len;
|
||||||
|
|
||||||
|
/* Audio track (populated when mp4a trak found) */
|
||||||
|
uint32_t audio_format; /* fourcc: 'mp4a' or 0 if no audio */
|
||||||
|
uint32_t audio_sample_rate; /* from mp4a sample entry */
|
||||||
|
uint8_t audio_codecdata[64]; /* AudioSpecificConfig from esds */
|
||||||
|
uint32_t audio_codecdata_len;
|
||||||
|
uint32_t audio_lead_trim; /* encoder priming from edts/elst */
|
||||||
|
|
||||||
|
/* Audio table counts used to size aac.codec's workspace. The AAC codec
|
||||||
|
* parses its own table data from the file. */
|
||||||
|
uint32_t audio_num_samples;
|
||||||
|
uint32_t audio_num_stsc;
|
||||||
|
uint32_t audio_num_stco;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Open and parse an MP4 file, extracting video and audio tracks.
|
||||||
|
*
|
||||||
|
* filepath: path to the .mp4/.m4v file
|
||||||
|
* res: output struct (caller allocates)
|
||||||
|
* sample_buf: buffer for video sample_sizes array
|
||||||
|
* sample_cap: number of uint32_t entries in sample_buf
|
||||||
|
* chunk_buf: buffer for video chunk_offsets array
|
||||||
|
* chunk_cap: number of uint32_t entries in chunk_buf
|
||||||
|
* stsc_buf: buffer for video sample-to-chunk entries
|
||||||
|
* stsc_cap: number of entries in stsc_buf
|
||||||
|
* sync_buf: buffer for video sync-sample entries
|
||||||
|
* sync_cap: number of entries in sync_buf
|
||||||
|
*
|
||||||
|
* Returns 0 on success (video found), -1 on error.
|
||||||
|
* Audio track is optional; check res->audio_format != 0. */
|
||||||
|
int mp4v_demux_open(const char *filepath,
|
||||||
|
struct mp4v_demux_res *res,
|
||||||
|
uint32_t *sample_buf, uint32_t sample_cap,
|
||||||
|
uint32_t *chunk_buf, uint32_t chunk_cap,
|
||||||
|
struct mp4v_stsc_entry *stsc_buf, uint32_t stsc_cap,
|
||||||
|
uint32_t *sync_buf, uint32_t sync_cap);
|
||||||
|
|
||||||
|
/* Get the file offset and size of video sample N (0-based).
|
||||||
|
* Returns 0 on success, -1 on error. */
|
||||||
|
int mp4v_get_sample_offset(const struct mp4v_demux_res *res,
|
||||||
|
uint32_t sample_index,
|
||||||
|
uint32_t *offset_out,
|
||||||
|
uint32_t *size_out);
|
||||||
|
|
||||||
|
/* Check if video sample N is a keyframe (sync sample).
|
||||||
|
* Returns true if it's a keyframe or if no stss table exists
|
||||||
|
* (meaning all samples are sync samples). */
|
||||||
|
bool mp4v_is_keyframe(const struct mp4v_demux_res *res,
|
||||||
|
uint32_t sample_index);
|
||||||
|
|
||||||
|
#endif /* MP4_DEMUX_H */
|
||||||
563
apps/plugins/h264_player/video_audio.c
Normal file
563
apps/plugins/h264_player/video_audio.c
Normal file
|
|
@ -0,0 +1,563 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
****************************************************************************/
|
||||||
|
#include "plugin.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_HW_H264
|
||||||
|
|
||||||
|
#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->filesize(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_HW_H264 */
|
||||||
60
apps/plugins/h264_player/video_audio.h
Normal file
60
apps/plugins/h264_player/video_audio.h
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* H.264 player audio decode service.
|
||||||
|
*
|
||||||
|
* Decodes AAC audio via Rockbox's aac.codec and feeds a PCM ring buffer.
|
||||||
|
* Uses the audio clock from video_pcm for A/V synchronization.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
****************************************************************************/
|
||||||
|
#ifndef VIDEO_AUDIO_H
|
||||||
|
#define VIDEO_AUDIO_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "mp4_demux.h"
|
||||||
|
|
||||||
|
/* Return enough codec heap to retain every AAC chunk offset. Apple M4V files
|
||||||
|
* interleave audio and video densely, so a sparse chunk map cannot decode the
|
||||||
|
* audio stream correctly. Returns 0 if the size cannot be represented. */
|
||||||
|
size_t video_audio_workspace_size(const struct mp4v_demux_res *demux);
|
||||||
|
|
||||||
|
/* Initialize audio decoder and thread.
|
||||||
|
* filepath: MP4 file path (opens its own fd for independent seeks)
|
||||||
|
* demux: parsed MP4 with audio track data (audio_format != 0)
|
||||||
|
* Returns 0 on success, -1 on error. */
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* Start audio playback (begins decoding and filling PCM buffer). */
|
||||||
|
void video_audio_play(void);
|
||||||
|
|
||||||
|
/* Pause audio decode (PCM buffer drains, then silence). */
|
||||||
|
void video_audio_pause(void);
|
||||||
|
|
||||||
|
/* Resume audio decode after pause. */
|
||||||
|
void video_audio_resume(void);
|
||||||
|
|
||||||
|
/* Seek audio to target time in milliseconds. Flushes PCM buffer. */
|
||||||
|
void video_audio_seek(uint32_t target_ms);
|
||||||
|
|
||||||
|
/* Stop audio thread and release resources. Blocks until thread exits. */
|
||||||
|
void video_audio_stop(void);
|
||||||
|
|
||||||
|
/* Check if audio thread has pre-filled enough PCM data to start. */
|
||||||
|
bool video_audio_ready(void);
|
||||||
|
|
||||||
|
/* Check if audio thread is actively decoding (false after EOF or error). */
|
||||||
|
bool video_audio_is_active(void);
|
||||||
|
|
||||||
|
/* True if the dynamically loaded AAC codec failed to load or decode. */
|
||||||
|
bool video_audio_failed(void);
|
||||||
|
|
||||||
|
#endif /* VIDEO_AUDIO_H */
|
||||||
250
apps/plugins/h264_player/video_pcm.c
Normal file
250
apps/plugins/h264_player/video_pcm.c
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* PCM ring buffer for video player audio output.
|
||||||
|
*
|
||||||
|
* Simplified version of mpegplayer's pcm_output.c.
|
||||||
|
* Single-producer (audio decode thread), single-consumer (DMA ISR).
|
||||||
|
* Audio-master clock via monotonic sample counter.
|
||||||
|
*
|
||||||
|
* get_more() runs in DMA ISR context: no yield, sleep, mutex, or file I/O.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
****************************************************************************/
|
||||||
|
#include "plugin.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_HW_H264
|
||||||
|
|
||||||
|
#include "system.h"
|
||||||
|
#include "kernel.h"
|
||||||
|
#include "pcm.h"
|
||||||
|
#include "pcm_mixer.h"
|
||||||
|
#include "pcm_sampr.h"
|
||||||
|
#include "video_pcm.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Silence clip for underrun (256 stereo samples = ~5.8ms at 44.1kHz) */
|
||||||
|
#define SILENCE_SAMPLES 256
|
||||||
|
|
||||||
|
static int16_t *pcm_buffer;
|
||||||
|
static uint32_t pcm_buffer_frames;
|
||||||
|
static int16_t silence[SILENCE_SAMPLES * 2];
|
||||||
|
|
||||||
|
/* Read/write positions in stereo samples (wrap via modulo) */
|
||||||
|
static volatile uint32_t pcm_read_pos;
|
||||||
|
static volatile uint32_t pcm_write_pos;
|
||||||
|
|
||||||
|
/* Master clock: stereo samples consumed by DMA (uint32_t sufficient
|
||||||
|
* for >27 hours at 44.1kHz; avoids torn reads on 32-bit ARM) */
|
||||||
|
static volatile uint32_t clock_samples;
|
||||||
|
|
||||||
|
/* Clock base: absolute time (ms) at last flush/init */
|
||||||
|
static uint32_t clock_base_ms;
|
||||||
|
static uint32_t clock_sample_rate;
|
||||||
|
|
||||||
|
/* Flush guard: prevents write during flush (set by main, checked by audio) */
|
||||||
|
static volatile bool flush_pending;
|
||||||
|
static struct mutex write_mutex;
|
||||||
|
|
||||||
|
/* Saved mixer frequency for restore on stop */
|
||||||
|
static unsigned int saved_sampr;
|
||||||
|
|
||||||
|
static uint32_t pcm_used(void)
|
||||||
|
{
|
||||||
|
return pcm_write_pos - pcm_read_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t pcm_free(void)
|
||||||
|
{
|
||||||
|
uint32_t used = pcm_used();
|
||||||
|
|
||||||
|
return used < pcm_buffer_frames ? pcm_buffer_frames - used : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DMA callback runs in ISR context.
|
||||||
|
* Pulls from ring buffer, advances clock. On underrun, plays silence. */
|
||||||
|
static void video_pcm_get_more(const void **start, size_t *size)
|
||||||
|
{
|
||||||
|
uint32_t avail = pcm_used();
|
||||||
|
|
||||||
|
if (avail > 0)
|
||||||
|
{
|
||||||
|
uint32_t rd = pcm_read_pos % pcm_buffer_frames;
|
||||||
|
uint32_t chunk = avail;
|
||||||
|
|
||||||
|
/* Don't wrap around the buffer end */
|
||||||
|
if (rd + chunk > pcm_buffer_frames)
|
||||||
|
chunk = pcm_buffer_frames - rd;
|
||||||
|
|
||||||
|
/* Limit chunk to one mixer frame so the entire returned buffer
|
||||||
|
* is consumed before pcm_read_pos advances. Without this, the
|
||||||
|
* mixer stores our pointer and gradually reads over 8 callbacks
|
||||||
|
* while the write side sees the positions as "free" and
|
||||||
|
* overwrites them, causing audible fragment repetition. */
|
||||||
|
if (chunk > MIX_FRAME_SAMPLES)
|
||||||
|
chunk = MIX_FRAME_SAMPLES;
|
||||||
|
|
||||||
|
*start = &pcm_buffer[rd * 2];
|
||||||
|
*size = chunk * 4; /* stereo 16-bit = 4 bytes per sample */
|
||||||
|
pcm_read_pos += chunk;
|
||||||
|
clock_samples += chunk;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Underrun: play silence. Do NOT advance the clock;
|
||||||
|
* advancing would create a permanent A/V offset because the
|
||||||
|
* audio content hasn't actually been played yet. */
|
||||||
|
*start = silence;
|
||||||
|
*size = sizeof(silence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct mixer_play_cbs video_pcm_cbs = {
|
||||||
|
.get_more = video_pcm_get_more,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool video_pcm_init(uint32_t sample_rate, void *buffer, size_t buffer_size)
|
||||||
|
{
|
||||||
|
if (sample_rate == 0 || buffer == NULL ||
|
||||||
|
buffer_size / (2 * sizeof(int16_t)) < MIX_FRAME_SAMPLES)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Save current frequency for restore */
|
||||||
|
saved_sampr = rb->mixer_get_frequency();
|
||||||
|
pcm_buffer = buffer;
|
||||||
|
pcm_buffer_frames = buffer_size / (2 * sizeof(int16_t));
|
||||||
|
|
||||||
|
/* Reset buffer state */
|
||||||
|
pcm_read_pos = 0;
|
||||||
|
pcm_write_pos = 0;
|
||||||
|
clock_samples = 0;
|
||||||
|
clock_base_ms = 0;
|
||||||
|
clock_sample_rate = sample_rate;
|
||||||
|
flush_pending = false;
|
||||||
|
rb->mutex_init(&write_mutex);
|
||||||
|
rb->memset(silence, 0, sizeof(silence));
|
||||||
|
|
||||||
|
/* Set sample rate BEFORE starting playback
|
||||||
|
* (mixer_set_frequency calls mixer_reset which stops all channels) */
|
||||||
|
#if INPUT_SRC_CAPS != 0
|
||||||
|
rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
|
||||||
|
rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
|
||||||
|
#endif
|
||||||
|
rb->mixer_set_frequency(sample_rate);
|
||||||
|
|
||||||
|
/* Start playback with DMA callback */
|
||||||
|
rb->mixer_channel_set_amplitude(PCM_MIXER_CHAN_PLAYBACK, MIX_AMP_UNITY);
|
||||||
|
rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK,
|
||||||
|
&video_pcm_cbs, NULL, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int video_pcm_write(const int16_t *pcm, int stereo_samples)
|
||||||
|
{
|
||||||
|
int written = 0;
|
||||||
|
|
||||||
|
rb->mutex_lock(&write_mutex);
|
||||||
|
if (flush_pending)
|
||||||
|
{
|
||||||
|
rb->mutex_unlock(&write_mutex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (written < stereo_samples)
|
||||||
|
{
|
||||||
|
uint32_t space = pcm_free();
|
||||||
|
if (space == 0 || flush_pending)
|
||||||
|
break;
|
||||||
|
|
||||||
|
uint32_t wr = pcm_write_pos % pcm_buffer_frames;
|
||||||
|
uint32_t chunk = stereo_samples - written;
|
||||||
|
|
||||||
|
if (chunk > space)
|
||||||
|
chunk = space;
|
||||||
|
|
||||||
|
/* Don't wrap around the buffer end */
|
||||||
|
if (wr + chunk > pcm_buffer_frames)
|
||||||
|
chunk = pcm_buffer_frames - wr;
|
||||||
|
|
||||||
|
rb->memcpy(&pcm_buffer[wr * 2], &pcm[written * 2], chunk * 4);
|
||||||
|
pcm_write_pos += chunk;
|
||||||
|
written += chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
rb->mutex_unlock(&write_mutex);
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t video_pcm_get_clock_ms(void)
|
||||||
|
{
|
||||||
|
uint32_t samples = clock_samples; /* atomic 32-bit read on ARM */
|
||||||
|
if (clock_sample_rate == 0)
|
||||||
|
return clock_base_ms;
|
||||||
|
return clock_base_ms + (uint32_t)((uint64_t)samples * 1000
|
||||||
|
/ clock_sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void video_pcm_flush(uint32_t base_ms, uint32_t sample_rate)
|
||||||
|
{
|
||||||
|
/* Serialize against the codec producer before resetting its indexes. */
|
||||||
|
rb->mutex_lock(&write_mutex);
|
||||||
|
flush_pending = true;
|
||||||
|
|
||||||
|
/* Stop mixer channel to invalidate any stale buffer pointer,
|
||||||
|
* then reset state, then restart (mpegplayer pattern). */
|
||||||
|
rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK);
|
||||||
|
|
||||||
|
pcm_read_pos = 0;
|
||||||
|
pcm_write_pos = 0;
|
||||||
|
clock_samples = 0;
|
||||||
|
clock_base_ms = base_ms;
|
||||||
|
clock_sample_rate = sample_rate;
|
||||||
|
|
||||||
|
/* Restart mixer with fresh callback */
|
||||||
|
rb->mixer_channel_set_amplitude(PCM_MIXER_CHAN_PLAYBACK, MIX_AMP_UNITY);
|
||||||
|
rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK,
|
||||||
|
&video_pcm_cbs, NULL, 0);
|
||||||
|
|
||||||
|
flush_pending = false;
|
||||||
|
rb->mutex_unlock(&write_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void video_pcm_pause(bool pause)
|
||||||
|
{
|
||||||
|
rb->mixer_channel_play_pause(PCM_MIXER_CHAN_PLAYBACK, !pause);
|
||||||
|
}
|
||||||
|
|
||||||
|
void video_pcm_stop(void)
|
||||||
|
{
|
||||||
|
flush_pending = false;
|
||||||
|
|
||||||
|
/* Serialize callback removal before plugin-owned memory is released. */
|
||||||
|
rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK);
|
||||||
|
|
||||||
|
/* Restore original sample rate */
|
||||||
|
if (saved_sampr != 0)
|
||||||
|
rb->mixer_set_frequency(saved_sampr);
|
||||||
|
pcm_buffer = NULL;
|
||||||
|
pcm_buffer_frames = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool video_pcm_has_space(int stereo_samples)
|
||||||
|
{
|
||||||
|
return pcm_free() >= (uint32_t)stereo_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool video_pcm_empty(void)
|
||||||
|
{
|
||||||
|
return pcm_used() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t video_pcm_buffered_samples(void)
|
||||||
|
{
|
||||||
|
return pcm_used();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_HW_H264 */
|
||||||
52
apps/plugins/h264_player/video_pcm.h
Normal file
52
apps/plugins/h264_player/video_pcm.h
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* PCM ring buffer for H.264 player audio output.
|
||||||
|
*
|
||||||
|
* Simplified version of mpegplayer's pcm_output.c.
|
||||||
|
* Audio-master clock: DMA callback advances clock_samples monotonically.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
****************************************************************************/
|
||||||
|
#ifndef VIDEO_PCM_H
|
||||||
|
#define VIDEO_PCM_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/* Initialize PCM output at the given sample rate.
|
||||||
|
* Saves and changes mixer frequency. */
|
||||||
|
bool video_pcm_init(uint32_t sample_rate, void *buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
/* Write interleaved stereo int16 PCM to the ring buffer.
|
||||||
|
* Returns number of stereo samples actually written (may be less if full). */
|
||||||
|
int video_pcm_write(const int16_t *pcm, int stereo_samples);
|
||||||
|
|
||||||
|
/* Get audio master clock in milliseconds.
|
||||||
|
* Returns base_ms + (samples_played * 1000 / sample_rate). */
|
||||||
|
uint32_t video_pcm_get_clock_ms(void);
|
||||||
|
|
||||||
|
/* Flush ring buffer and set clock base (for seek).
|
||||||
|
* After flush, get_clock returns base_ms + samples_played. */
|
||||||
|
void video_pcm_flush(uint32_t base_ms, uint32_t sample_rate);
|
||||||
|
|
||||||
|
/* Pause/resume PCM output. */
|
||||||
|
void video_pcm_pause(bool pause);
|
||||||
|
|
||||||
|
/* Stop PCM output and restore original mixer frequency. */
|
||||||
|
void video_pcm_stop(void);
|
||||||
|
|
||||||
|
/* Check if ring buffer has space for at least n stereo samples. */
|
||||||
|
bool video_pcm_has_space(int stereo_samples);
|
||||||
|
|
||||||
|
/* Check if ring buffer is empty (all data played). */
|
||||||
|
bool video_pcm_empty(void);
|
||||||
|
|
||||||
|
/* Return number of stereo samples currently buffered. */
|
||||||
|
uint32_t video_pcm_buffered_samples(void);
|
||||||
|
|
||||||
|
#endif /* VIDEO_PCM_H */
|
||||||
1132
apps/plugins/h264_player/video_playback.c
Normal file
1132
apps/plugins/h264_player/video_playback.c
Normal file
File diff suppressed because it is too large
Load diff
21
apps/plugins/h264_player/video_playback.h
Normal file
21
apps/plugins/h264_player/video_playback.h
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* Hardware H.264 player implementation.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
****************************************************************************/
|
||||||
|
#ifndef VIDEO_PLAYBACK_H
|
||||||
|
#define VIDEO_PLAYBACK_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* Play a validated H.264 Baseline/AAC-LC MP4 from caller-owned memory.
|
||||||
|
* The caller must retain the plugin audio buffer until this function returns.
|
||||||
|
* Returns 0 at end of stream, 1 on user exit, 2 for USB, or -1 on failure. */
|
||||||
|
int video_h264_play(const char *filepath, void *buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
#endif /* VIDEO_PLAYBACK_H */
|
||||||
|
|
@ -76,6 +76,11 @@ wav,viewers/wavview,10
|
||||||
wav,viewers/wav2wv,-
|
wav,viewers/wav2wv,-
|
||||||
wav,viewers/mp3_encoder,-
|
wav,viewers/mp3_encoder,-
|
||||||
wav,viewers/test_codec,-
|
wav,viewers/test_codec,-
|
||||||
|
#ifdef HAVE_HW_H264
|
||||||
|
mp4,viewers/h264_player,4
|
||||||
|
m4v,viewers/h264_player,4
|
||||||
|
mov,viewers/h264_player,4
|
||||||
|
#endif
|
||||||
mpg,viewers/mpegplayer,4
|
mpg,viewers/mpegplayer,4
|
||||||
mpeg,viewers/mpegplayer,4
|
mpeg,viewers/mpegplayer,4
|
||||||
mpv,viewers/mpegplayer,4
|
mpv,viewers/mpegplayer,4
|
||||||
|
|
|
||||||
|
|
@ -767,6 +767,7 @@ Michael McAllister
|
||||||
Hemant Kumar
|
Hemant Kumar
|
||||||
Jerry Shen
|
Jerry Shen
|
||||||
Olivier Senn
|
Olivier Senn
|
||||||
|
David Cormier
|
||||||
|
|
||||||
The libmad team
|
The libmad team
|
||||||
The wavpack team
|
The wavpack team
|
||||||
|
|
|
||||||
|
|
@ -1680,6 +1680,10 @@ target/arm/s5l8702/ipod6g/adc-6g.c
|
||||||
target/arm/s5l8702/ipod6g/powermgmt-6g.c
|
target/arm/s5l8702/ipod6g/powermgmt-6g.c
|
||||||
target/arm/s5l8702/ipod6g/power-6g.c
|
target/arm/s5l8702/ipod6g/power-6g.c
|
||||||
target/arm/s5l8702/ipod6g/storage_ata-6g.c
|
target/arm/s5l8702/ipod6g/storage_ata-6g.c
|
||||||
|
#if defined(HAVE_HW_H264) && !defined(BOOTLOADER)
|
||||||
|
target/arm/s5l8702/ipod6g/vpu-6g.c
|
||||||
|
target/arm/s5l8702/ipod6g/vpu-h264-6g.c
|
||||||
|
#endif
|
||||||
#if (CONFIG_RTC == RTC_NANO2G)
|
#if (CONFIG_RTC == RTC_NANO2G)
|
||||||
target/arm/s5l8702/ipod6g/rtc-6g.c
|
target/arm/s5l8702/ipod6g/rtc-6g.c
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,11 @@
|
||||||
/* define this to enable JPEG decoding */
|
/* define this to enable JPEG decoding */
|
||||||
#define HAVE_JPEG
|
#define HAVE_JPEG
|
||||||
|
|
||||||
|
/* S5L8702 VPU-B hardware H.264 decoder. */
|
||||||
|
#ifndef SIMULATOR
|
||||||
|
#define HAVE_HW_H264
|
||||||
|
#endif
|
||||||
|
|
||||||
/* define this if you can invert the colours on your LCD */
|
/* define this if you can invert the colours on your LCD */
|
||||||
//#define HAVE_LCD_INVERT
|
//#define HAVE_LCD_INVERT
|
||||||
|
|
||||||
|
|
|
||||||
39
firmware/export/hw_h264.h
Normal file
39
firmware/export/hw_h264.h
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* Hardware H.264 decoder interface exposed to viewer plugins.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef HW_H264_H
|
||||||
|
#define HW_H264_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct vpu_h264;
|
||||||
|
|
||||||
|
/* The container, audio, synchronization, and UI layers live in the plugin.
|
||||||
|
* Only the target hardware decoder is exported by core. */
|
||||||
|
struct hw_h264_api
|
||||||
|
{
|
||||||
|
size_t (*buf_size)(int max_w, int max_h);
|
||||||
|
struct vpu_h264 *(*open)(void *buf, size_t buf_size,
|
||||||
|
int max_w, int max_h);
|
||||||
|
int (*configure)(struct vpu_h264 *decoder,
|
||||||
|
const uint8_t *avcc, int avcc_len);
|
||||||
|
int (*decode_sample)(struct vpu_h264 *decoder, const uint8_t *sample,
|
||||||
|
int sample_len, int nalu_len_size);
|
||||||
|
void (*get_frame)(const struct vpu_h264 *decoder,
|
||||||
|
const uint8_t **y, const uint8_t **cb,
|
||||||
|
const uint8_t **cr, int *w, int *h, int *stride);
|
||||||
|
void (*close)(struct vpu_h264 *decoder);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct hw_h264_api target_hw_h264_api;
|
||||||
|
|
||||||
|
#endif /* HW_H264_H */
|
||||||
|
|
@ -1643,6 +1643,28 @@ The following peripherals are not present in the Samsung S5L8700 datasheet.
|
||||||
Information for them was gathered solely by reverse-engineering Apple's firmware.
|
Information for them was gathered solely by reverse-engineering Apple's firmware.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* VPU-B H.264 decoder - S5L8702 */
|
||||||
|
#if CONFIG_CPU == S5L8702
|
||||||
|
#define VPU_MODE (*((REG32_PTR_T)(0x38100314)))
|
||||||
|
#define VPU_BASE 0x39800000
|
||||||
|
#define VPU_REG(off) (*((REG32_PTR_T)(VPU_BASE + (off))))
|
||||||
|
|
||||||
|
#define VPU_DPB_Y(i) VPU_REG((i) * 12)
|
||||||
|
#define VPU_DPB_CB(i) VPU_REG((i) * 12 + 4)
|
||||||
|
#define VPU_DPB_CR(i) VPU_REG((i) * 12 + 8)
|
||||||
|
#define VPU_OUT_Y VPU_REG(0x0cc)
|
||||||
|
#define VPU_OUT_CB VPU_REG(0x0d0)
|
||||||
|
#define VPU_OUT_CR VPU_REG(0x0d4)
|
||||||
|
#define VPU_CTRL_BUF VPU_REG(0x0d8)
|
||||||
|
#define VPU_SLICE_DESC VPU_REG(0x0dc)
|
||||||
|
#define VPU_DIMS VPU_REG(0x0e0)
|
||||||
|
#define VPU_STRIDES VPU_REG(0x0e4)
|
||||||
|
#define VPU_CTRL VPU_REG(0x0e8)
|
||||||
|
#define VPU_STATUS0 VPU_REG(0x0f0)
|
||||||
|
#define VPU_STATUS1 VPU_REG(0x0f4)
|
||||||
|
#define VPU_CONFIG VPU_REG(0x118)
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Hardware AES crypto unit - S5L8701+ */
|
/* Hardware AES crypto unit - S5L8701+ */
|
||||||
#if CONFIG_CPU==S5L8701
|
#if CONFIG_CPU==S5L8701
|
||||||
#define AES_BASE 0x39800000
|
#define AES_BASE 0x39800000
|
||||||
|
|
|
||||||
71
firmware/target/arm/s5l8702/ipod6g/vpu-6g.c
Normal file
71
firmware/target/arm/s5l8702/ipod6g/vpu-6g.c
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
*
|
||||||
|
* S5L8702 VPU-B IRQ 35 completion handler.
|
||||||
|
*
|
||||||
|
* IRQ 35 is VIC1 bit 3 and is asserted when VPU-B completes a decode.
|
||||||
|
* The handler captures the completion status, acknowledges the edge, and
|
||||||
|
* releases a semaphore for the decoder driver.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "kernel.h"
|
||||||
|
#include "s5l87xx.h"
|
||||||
|
|
||||||
|
#define VPU_IRQ_BIT (1u << 3)
|
||||||
|
|
||||||
|
static struct semaphore vpu_complete;
|
||||||
|
static volatile uint32_t vpu_last_status1;
|
||||||
|
|
||||||
|
void vpu_irq_init(void)
|
||||||
|
{
|
||||||
|
semaphore_init(&vpu_complete, 1, 0);
|
||||||
|
vpu_last_status1 = 0;
|
||||||
|
VIC1INTENCLEAR = VPU_IRQ_BIT;
|
||||||
|
VIC1EDGE0 |= VPU_IRQ_BIT;
|
||||||
|
VIC1EDGE1 = VPU_IRQ_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vpu_irq_arm(void)
|
||||||
|
{
|
||||||
|
semaphore_wait(&vpu_complete, 0);
|
||||||
|
VIC1EDGE1 = VPU_IRQ_BIT;
|
||||||
|
VIC1INTENABLE = VPU_IRQ_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vpu_irq_wait(int timeout_ticks)
|
||||||
|
{
|
||||||
|
int ret = semaphore_wait(&vpu_complete, timeout_ticks);
|
||||||
|
VIC1INTENCLEAR = VPU_IRQ_BIT;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t vpu_irq_status1(void)
|
||||||
|
{
|
||||||
|
return vpu_last_status1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICODE_ATTR INT_IRQ35(void)
|
||||||
|
{
|
||||||
|
vpu_last_status1 = VPU_STATUS1;
|
||||||
|
VIC1EDGE1 = VPU_IRQ_BIT;
|
||||||
|
VIC1INTENCLEAR = VPU_IRQ_BIT;
|
||||||
|
semaphore_release(&vpu_complete);
|
||||||
|
}
|
||||||
48
firmware/target/arm/s5l8702/ipod6g/vpu-6g.h
Normal file
48
firmware/target/arm/s5l8702/ipod6g/vpu-6g.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
*
|
||||||
|
* S5L8702 VPU-B IRQ completion for H.264 hardware decoder.
|
||||||
|
*
|
||||||
|
* VPU-B asserts IRQ 35 (VIC1 bit 3, edge-triggered) on decode completion.
|
||||||
|
* These functions provide semaphore-based synchronous wait, replacing
|
||||||
|
* the sleep(200ms) polling approach.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef VPU_6G_H
|
||||||
|
#define VPU_6G_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Initialize VPU-B IRQ 35: setup semaphore, configure edge trigger.
|
||||||
|
* Call once before first decode. */
|
||||||
|
void vpu_irq_init(void);
|
||||||
|
|
||||||
|
/* Arm for next decode: drain stale semaphore signal, clear pending
|
||||||
|
* edge, enable IRQ 35. Call BEFORE triggering VPU decode. */
|
||||||
|
void vpu_irq_arm(void);
|
||||||
|
|
||||||
|
/* Wait for VPU decode completion. Blocks until ISR fires or timeout.
|
||||||
|
* Disables IRQ 35 after return. Returns OBJ_WAIT_TIMEDOUT on timeout,
|
||||||
|
* OBJ_WAIT_SUCCEEDED on success. */
|
||||||
|
int vpu_irq_wait(int timeout_ticks);
|
||||||
|
|
||||||
|
/* Get STATUS1 value captured by ISR during last completion. */
|
||||||
|
uint32_t vpu_irq_status1(void);
|
||||||
|
|
||||||
|
#endif /* VPU_6G_H */
|
||||||
1081
firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.c
Normal file
1081
firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.c
Normal file
File diff suppressed because it is too large
Load diff
84
firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.h
Normal file
84
firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.h
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025-2026 David Cormier
|
||||||
|
*
|
||||||
|
* S5L8702 VPU-B H.264 Baseline hardware decoder.
|
||||||
|
*
|
||||||
|
* Drives the VPU-B block on iPod Classic 6G (S5L8702).
|
||||||
|
* Full hardware decode: CAVLC, dequant, IDCT, intra/inter prediction,
|
||||||
|
* motion compensation, and in-loop deblocking.
|
||||||
|
*
|
||||||
|
* Supports: H.264 Baseline profile, level <= 3.0, up to ten ordered slices
|
||||||
|
* per frame. The single-slice path is proven bit-perfect against ffmpeg;
|
||||||
|
* multi-slice support follows the same Apple VPU descriptor interface.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef VPU_H264_6G_H
|
||||||
|
#define VPU_H264_6G_H
|
||||||
|
|
||||||
|
#include "hw_h264.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* Returns minimum buffer size needed for given max dimensions.
|
||||||
|
* Caller must provide a contiguous buffer of at least this size
|
||||||
|
* to vpu_h264_open(). Accounts for alignment padding. */
|
||||||
|
size_t vpu_h264_buf_size(int max_w, int max_h);
|
||||||
|
|
||||||
|
/* Initialize decoder. Allocates internal buffers from buf via bump
|
||||||
|
* allocator. Powers on VPU-B hardware and IRQ.
|
||||||
|
* Returns context pointer (inside buf) or NULL on error. */
|
||||||
|
struct vpu_h264 *vpu_h264_open(void *buf, size_t buf_size,
|
||||||
|
int max_w, int max_h);
|
||||||
|
|
||||||
|
/* Feed avcC decoder configuration (from MP4 container).
|
||||||
|
* Parses SPS and PPS NALUs from the avcC blob.
|
||||||
|
* Returns 0 on success, -1 on error. */
|
||||||
|
int vpu_h264_configure(struct vpu_h264 *v,
|
||||||
|
const uint8_t *avcc, int avcc_len);
|
||||||
|
|
||||||
|
/* Feed one raw NALU (including nal_header byte, excluding length prefix).
|
||||||
|
* Accepts EBSP (emulation prevention bytes intact).
|
||||||
|
* Handles SPS (7), PPS (8), IDR slice (5), non-IDR slice (1) internally.
|
||||||
|
* For slice NALUs: triggers synchronous HW decode (~5ms).
|
||||||
|
*
|
||||||
|
* Returns: 1 = frame decoded (call vpu_h264_get_frame)
|
||||||
|
* 0 = NALU consumed (SPS/PPS/SEI, no frame output)
|
||||||
|
* -1 = error (STATUS1 bits or timeout) */
|
||||||
|
int vpu_h264_decode_nalu(struct vpu_h264 *v,
|
||||||
|
const uint8_t *nalu, int nalu_len);
|
||||||
|
|
||||||
|
/* Decode one length-prefixed AVC sample as a complete picture. All slices
|
||||||
|
* are submitted together, matching the two-slice files produced by iTunes'
|
||||||
|
* "Create iPod or iPhone Version" action. */
|
||||||
|
int vpu_h264_decode_sample(struct vpu_h264 *v, const uint8_t *sample,
|
||||||
|
int sample_len, int nalu_len_size);
|
||||||
|
|
||||||
|
/* Get pointers to last decoded frame planes (YCbCr 4:2:0 planar).
|
||||||
|
* Pointers are cached aliases; cache is already invalidated.
|
||||||
|
* stride is the coded luma pitch; w/h are the cropped display dimensions.
|
||||||
|
* Valid until the next decode call. */
|
||||||
|
void vpu_h264_get_frame(const struct vpu_h264 *v,
|
||||||
|
const uint8_t **y, const uint8_t **cb,
|
||||||
|
const uint8_t **cr, int *w, int *h, int *stride);
|
||||||
|
|
||||||
|
/* Shutdown decoder. Powers off VPU-B hardware. */
|
||||||
|
void vpu_h264_close(struct vpu_h264 *v);
|
||||||
|
|
||||||
|
#endif /* VPU_H264_6G_H */
|
||||||
|
|
@ -110,6 +110,9 @@ static const struct clocking_mode clk_modes[] =
|
||||||
/* cdiv hdiv hprat hsdiv */ /* CClk HClk PClk SM1Clk FPS */
|
/* cdiv hdiv hprat hsdiv */ /* CClk HClk PClk SM1Clk FPS */
|
||||||
{ 1, 2, 2, 4 }, /* 216 108 54 27 42 */
|
{ 1, 2, 2, 4 }, /* 216 108 54 27 42 */
|
||||||
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
||||||
|
/* Preserve the qualified intermediate step used when changing between
|
||||||
|
* the normal and boosted clocks. */
|
||||||
|
{ 2, 2, 2, 4 }, /* 108 108 54 27 */
|
||||||
{ 4, 4, 2, 2 }, /* 54 54 27 27 21 */
|
{ 4, 4, 2, 2 }, /* 54 54 27 27 21 */
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
\edef\UseOption{\UseOption,HAVE_DISK_STORAGE}
|
\edef\UseOption{\UseOption,HAVE_DISK_STORAGE}
|
||||||
\edef\UseOption{\UseOption,HAVE_CAR_ADAPTER_MODE}
|
\edef\UseOption{\UseOption,HAVE_CAR_ADAPTER_MODE}
|
||||||
\edef\UseOption{\UseOption,HAVE_COMPOSITE_VIDEO_OUT}
|
\edef\UseOption{\UseOption,HAVE_COMPOSITE_VIDEO_OUT}
|
||||||
|
\edef\UseOption{\UseOption,HAVE_HW_H264}
|
||||||
\edef\UseOption{\UseOption,ipod}
|
\edef\UseOption{\UseOption,ipod}
|
||||||
|
|
||||||
\newcommand{\playerman}{Ipod}
|
\newcommand{\playerman}{Ipod}
|
||||||
|
|
|
||||||
26
manual/plugins/h264_player.tex
Normal file
26
manual/plugins/h264_player.tex
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
\subsection{H.264 Player}
|
||||||
|
|
||||||
|
The H.264 Player uses a supported player's hardware video decoder to play
|
||||||
|
H.264 video from \fname{.mp4}, \fname{.m4v}, and \fname{.mov} files. It
|
||||||
|
supports H.264 Baseline Profile through level 3.0 at resolutions up to
|
||||||
|
640x480. AAC-LC audio is supported.
|
||||||
|
|
||||||
|
Select a supported file in the Rockbox \setting{File Browser} to start
|
||||||
|
playback. Playback resumes from the last saved position unless the previous
|
||||||
|
playback reached the end or the final five percent of the file.
|
||||||
|
|
||||||
|
\begin{btnmap}
|
||||||
|
\ActionWpsPlay
|
||||||
|
& Pause / Resume\\
|
||||||
|
\ActionWpsVolUp{} / \ActionWpsVolDown
|
||||||
|
& Adjust volume up / down\\
|
||||||
|
\ActionWpsSkipPrev{} / \ActionWpsSkipNext
|
||||||
|
& Seek backward / forward by 10 seconds\\
|
||||||
|
\ActionWpsMenu
|
||||||
|
& Open the H.264 Player menu\\
|
||||||
|
\ActionWpsStop
|
||||||
|
& Exit the player\\
|
||||||
|
\end{btnmap}
|
||||||
|
|
||||||
|
The player menu can resume playback or exit the player. A resume point is
|
||||||
|
saved when playback is stopped before the final five percent of the file.
|
||||||
|
|
@ -182,6 +182,9 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).}
|
||||||
& \fname{.s3m, .stm, .stx, .ult,} & \\
|
& \fname{.s3m, .stm, .stx, .ult,} & \\
|
||||||
& \fname{.uni, .umx, .xm} & \\
|
& \fname{.uni, .umx, .xm} & \\
|
||||||
MPEG Player & \fname{.mpg, .mpeg, .mpv, .m2v} & \\
|
MPEG Player & \fname{.mpg, .mpeg, .mpv, .m2v} & \\
|
||||||
|
}
|
||||||
|
\opt{HAVE_HW_H264}{
|
||||||
|
H.264 Player & \fname{.mp4, .m4v, .mov} & \\
|
||||||
}
|
}
|
||||||
MP3 Encoder & \fname{.wav} & x \\
|
MP3 Encoder & \fname{.wav} & x \\
|
||||||
Rockboy & \fname{.gb, .gbc} & \\
|
Rockboy & \fname{.gb, .gbc} & \\
|
||||||
|
|
@ -211,6 +214,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).}
|
||||||
|
|
||||||
\nopt{lowmem}{\input{plugins/mpegplayer.tex}}
|
\nopt{lowmem}{\input{plugins/mpegplayer.tex}}
|
||||||
|
|
||||||
|
\opt{HAVE_HW_H264}{\input{plugins/h264_player.tex}}
|
||||||
|
|
||||||
\input{plugins/mp3_encoder.tex}
|
\input{plugins/mp3_encoder.tex}
|
||||||
|
|
||||||
\opt{iriverh300,iriverh100,SANSA_FUZE_PAD,SANSA_E200_PAD,IPOD_4G_PAD,IPOD_3G_PAD%
|
\opt{iriverh300,iriverh100,SANSA_FUZE_PAD,SANSA_E200_PAD,IPOD_4G_PAD,IPOD_3G_PAD%
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue