DreamRoQ sample added

This commit is contained in:
lerabot 2023-08-22 18:56:30 -04:00
parent 452cda5a3b
commit 397cfa6aa9
10 changed files with 1858 additions and 0 deletions

View File

@ -0,0 +1,572 @@
/*
* Dreamroq by Mike Melanson
* Updated by Josh Pearson to add audio support
*
* This is the main playback engine.
*/
/*
Name:Ian micheal
Date: 15/08/23 08:16
Description: kos filesystem api port
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dc/fmath_base.h>
#include <kos/fs.h> // Include the KOS filesystem header
#include "dreamroqlib.h"
#define RoQ_INFO 0x1001
#define RoQ_QUAD_CODEBOOK 0x1002
#define RoQ_QUAD_VQ 0x1011
#define RoQ_SOUND_MONO 0x1020
#define RoQ_SOUND_STEREO 0x1021
#define RoQ_SIGNATURE 0x1084
#define CHUNK_HEADER_SIZE 8
#define LE_16(buf) (*buf | (*(buf+1) << 8))
#define LE_32(buf) (*buf | (*(buf+1) << 8) | (*(buf+2) << 16) | (*(buf+3) << 24))
#define MAX_BUF_SIZE (64 * 1024)
#define ROQ_CODEBOOK_SIZE 256
struct roq_audio
{
int pcm_samples;
int channels;
int position;
short snd_sqr_arr[260];
unsigned char pcm_sample[MAX_BUF_SIZE];
}roq_audio;
typedef struct
{
int width;
int height;
int mb_width;
int mb_height;
int mb_count;
int current_frame;
unsigned short *frame[2];
int stride;
int texture_height;
unsigned short cb2x2[ROQ_CODEBOOK_SIZE][4];
unsigned short cb4x4[ROQ_CODEBOOK_SIZE][16];
} roq_state;
static int roq_unpack_quad_codebook(unsigned char *buf, int size, int arg,
roq_state *state)
{
int y[4];
int yp, u, v;
int r, g, b;
int count2x2;
int count4x4;
int i, j;
unsigned short *v2x2;
unsigned short *v4x4;
count2x2 = (arg >> 8) & 0xFF;
count4x4 = arg & 0xFF;
if (!count2x2)
count2x2 = ROQ_CODEBOOK_SIZE;
/* 0x00 means 256 4x4 vectors iff there is enough space in the chunk
* after accounting for the 2x2 vectors */
if (!count4x4 && count2x2 * 6 < size)
count4x4 = ROQ_CODEBOOK_SIZE;
/* size sanity check */
if ((count2x2 * 6 + count4x4 * 4) != size)
{
return ROQ_BAD_CODEBOOK;
}
/* unpack the 2x2 vectors */
for (i = 0; i < count2x2; i++)
{
/* unpack the YUV components from the bytestream */
for (j = 0; j < 4; j++)
y[j] = *buf++;
u = *buf++;
v = *buf++;
/* convert to RGB565 */
for (j = 0; j < 4; j++)
{
yp = (y[j] - 16) * 1.164;
r = (yp + 1.596 * (v - 128)) / 8;
g = (yp - 0.813 * (v - 128) - 0.391 * (u - 128)) / 4;
b = (yp + 2.018 * (u - 128)) / 8;
if (r < 0) r = 0;
if (r > 31) r = 31;
if (g < 0) g = 0;
if (g > 63) g = 63;
if (b < 0) b = 0;
if (b > 31) b = 31;
state->cb2x2[i][j] = (
(r << 11) |
(g << 5) |
(b << 0) );
}
}
/* unpack the 4x4 vectors */
for (i = 0; i < count4x4; i++)
{
for (j = 0; j < 4; j++)
{
v2x2 = state->cb2x2[*buf++];
v4x4 = state->cb4x4[i] + (j / 2) * 8 + (j % 2) * 2;
v4x4[0] = v2x2[0];
v4x4[1] = v2x2[1];
v4x4[4] = v2x2[2];
v4x4[5] = v2x2[3];
}
}
return ROQ_SUCCESS;
}
#define GET_BYTE(x) \
if (index >= size) { \
status = ROQ_BAD_VQ_STREAM; \
x = 0; \
} else { \
x = buf[index++]; \
}
#define GET_MODE() \
if (!mode_count) { \
GET_BYTE(mode_lo); \
GET_BYTE(mode_hi); \
mode_set = (mode_hi << 8) | mode_lo; \
mode_count = 16; \
} \
mode_count -= 2; \
mode = (mode_set >> mode_count) & 0x03;
static int roq_unpack_vq(unsigned char *buf, int size, unsigned int arg,
roq_state *state)
{
int status = ROQ_SUCCESS;
int mb_x, mb_y;
int block; /* 8x8 blocks */
int subblock; /* 4x4 blocks */
int stride = state->stride;
int i;
/* frame and pixel management */
unsigned short *this_frame;
unsigned short *last_frame;
int line_offset;
int mb_offset;
int block_offset;
int subblock_offset;
unsigned short *this_ptr;
unsigned int *this_ptr32;
unsigned short *last_ptr;
/*unsigned int *last_ptr32;*/
unsigned short *vector16;
unsigned int *vector32;
int stride32 = stride / 2;
/* bytestream management */
int index = 0;
int mode_set = 0;
int mode, mode_lo, mode_hi;
int mode_count = 0;
/* vectors */
int mx, my;
int motion_x, motion_y;
unsigned char data_byte;
mx = (arg >> 8) & 0xFF;
my = arg & 0xFF;
if (state->current_frame == 1)
{
state->current_frame = 0;
this_frame = state->frame[0];
last_frame = state->frame[1];
}
else
{
state->current_frame = 1;
this_frame = state->frame[1];
last_frame = state->frame[0];
}
for (mb_y = 0; mb_y < state->mb_height && status == ROQ_SUCCESS; mb_y++)
{
line_offset = mb_y * 16 * stride;
for (mb_x = 0; mb_x < state->mb_width && status == ROQ_SUCCESS; mb_x++)
{
mb_offset = line_offset + mb_x * 16;
for (block = 0; block < 4 && status == ROQ_SUCCESS; block++)
{
block_offset = mb_offset + (block / 2 * 8 * stride) + (block % 2 * 8);
/* each 8x8 block gets a mode */
GET_MODE();
switch (mode)
{
case 0: /* MOT: skip */
break;
case 1: /* FCC: motion compensation */
/* this needs to be done 16 bits at a time due to
* data alignment issues on the SH-4 */
GET_BYTE(data_byte);
motion_x = 8 - (data_byte >> 4) - mx;
motion_y = 8 - (data_byte & 0xF) - my;
last_ptr = last_frame + block_offset +
(motion_y * stride) + motion_x;
this_ptr = this_frame + block_offset;
for (i = 0; i < 8; i++)
{
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
last_ptr += stride - 8;
this_ptr += stride - 8;
}
break;
case 2: /* SLD: upsample 4x4 vector */
GET_BYTE(data_byte);
vector16 = state->cb4x4[data_byte];
for (i = 0; i < 4*4; i++)
{
this_ptr = this_frame + block_offset +
(i / 4 * 2 * stride) + (i % 4 * 2);
this_ptr[0] = *vector16;
this_ptr[1] = *vector16;
this_ptr[stride+0] = *vector16;
this_ptr[stride+1] = *vector16;
vector16++;
}
break;
case 3: /* CCC: subdivide into 4 subblocks */
for (subblock = 0; subblock < 4; subblock++)
{
subblock_offset = block_offset + (subblock / 2 * 4 * stride) + (subblock % 2 * 4);
GET_MODE();
switch (mode)
{
case 0: /* MOT: skip */
break;
case 1: /* FCC: motion compensation */
GET_BYTE(data_byte);
motion_x = 8 - (data_byte >> 4) - mx;
motion_y = 8 - (data_byte & 0xF) - my;
last_ptr = last_frame + subblock_offset +
(motion_y * stride) + motion_x;
this_ptr = this_frame + subblock_offset;
for (i = 0; i < 4; i++)
{
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
*this_ptr++ = *last_ptr++;
last_ptr += stride - 4;
this_ptr += stride - 4;
}
break;;
case 2: /* SLD: use 4x4 vector from codebook */
GET_BYTE(data_byte);
vector32 = (unsigned int*)state->cb4x4[data_byte];
this_ptr32 = (unsigned int*)this_frame;
this_ptr32 += subblock_offset / 2;
for (i = 0; i < 4; i++)
{
*this_ptr32++ = *vector32++;
*this_ptr32++ = *vector32++;
this_ptr32 += stride32 - 2;
}
break;
case 3: /* CCC: subdivide into 4 subblocks */
GET_BYTE(data_byte);
vector16 = state->cb2x2[data_byte];
this_ptr = this_frame + subblock_offset;
this_ptr[0] = vector16[0];
this_ptr[1] = vector16[1];
this_ptr[stride+0] = vector16[2];
this_ptr[stride+1] = vector16[3];
GET_BYTE(data_byte);
vector16 = state->cb2x2[data_byte];
this_ptr[2] = vector16[0];
this_ptr[3] = vector16[1];
this_ptr[stride+2] = vector16[2];
this_ptr[stride+3] = vector16[3];
this_ptr += stride * 2;
GET_BYTE(data_byte);
vector16 = state->cb2x2[data_byte];
this_ptr[0] = vector16[0];
this_ptr[1] = vector16[1];
this_ptr[stride+0] = vector16[2];
this_ptr[stride+1] = vector16[3];
GET_BYTE(data_byte);
vector16 = state->cb2x2[data_byte];
this_ptr[2] = vector16[0];
this_ptr[3] = vector16[1];
this_ptr[stride+2] = vector16[2];
this_ptr[stride+3] = vector16[3];
break;
}
}
break;
}
}
}
}
/* sanity check to see if the stream was fully consumed */
if (status == ROQ_SUCCESS && index < size-2)
{
status = ROQ_BAD_VQ_STREAM;
}
return status;
}
/*
Name: Ian micheal
Copyright:
Author:
Date: 15/08/23 19:24
Description: ported from C normal file system to kos FS file system api because if this
Info from TapamN One issue you might run into is slow file access over ethernet.
Using the C library stdio.h functions (fread, fwrite) can be much slower than using the KOS filesystem calls directly (fs_read, fs_write) when reading/writing large blocks.
With stdio, you get something like tens of KB/sec, while with KOS you can get over 1 MB/sec. Stdio might be faster when preforming many very small operations.
dcload-serial doesn't have this issue.
*/
int dreamroq_play(char *filename, int loop, render_callback render_cb,
audio_callback audio_cb, quit_callback quit_cb)
{
file_t f;
ssize_t file_ret;
int framerate;
int chunk_id;
unsigned int chunk_size;
unsigned int chunk_arg;
roq_state state;
int status;
int initialized = 0;
unsigned char read_buffer[MAX_BUF_SIZE];
int i, snd_left, snd_right;
f = fs_open(filename, O_RDONLY);
if (f < 0)
return ROQ_FILE_OPEN_FAILURE;
file_ret = fs_read(f, read_buffer, CHUNK_HEADER_SIZE);
if (file_ret != CHUNK_HEADER_SIZE)
{
fs_close(f);
printf("\nROQ_FILE_READ_FAILURE\n\n");
return ROQ_FILE_READ_FAILURE;
}
framerate = LE_16(&read_buffer[6]);
printf("RoQ file plays at %d frames/sec\n", framerate);
/* Initialize Audio SQRT Look-Up Table */
for(i = 0; i < 128; i++)
{
roq_audio.snd_sqr_arr[i] = i * i;
roq_audio.snd_sqr_arr[i + 128] = -(i * i);
}
status = ROQ_SUCCESS;
while (1)
{
if (quit_cb && quit_cb())
break;
file_ret = fs_read(f, read_buffer, CHUNK_HEADER_SIZE);
#ifdef FPSGRAPH
printf("r\n");
#endif
if (file_ret < CHUNK_HEADER_SIZE)
{
if (file_ret == 0) // Indicates end of file
break;
else if (loop)
{
fs_seek(f, 8, SEEK_SET);
continue;
}
else
break;
}
chunk_id = LE_16(&read_buffer[0]);
chunk_size = LE_32(&read_buffer[2]);
chunk_arg = LE_16(&read_buffer[6]);
if (chunk_size > MAX_BUF_SIZE)
{
fs_close(f);
return ROQ_CHUNK_TOO_LARGE;
}
file_ret = fs_read(f, read_buffer, chunk_size);
if (file_ret != chunk_size)
{
status = ROQ_FILE_READ_FAILURE;
break;
}
switch(chunk_id)
{
case RoQ_INFO:
if (initialized)
continue;
state.width = LE_16(&read_buffer[0]);
state.height = LE_16(&read_buffer[2]);
/* width and height each need to be divisible by 16 */
if ((state.width & 0xF) || (state.height & 0xF))
{
status = ROQ_INVALID_PIC_SIZE;
break;
}
state.mb_width = state.width / 16;
state.mb_height = state.height / 16;
state.mb_count = state.mb_width * state.mb_height;
if (state.width < 8 || state.width > 1024)
status = ROQ_INVALID_DIMENSION;
else
{
state.stride = 8;
while (state.stride < state.width)
state.stride <<= 1;
}
if (state.height < 8 || state.height > 1024)
status = ROQ_INVALID_DIMENSION;
else
{
state.texture_height = 8;
while (state.texture_height < state.height)
state.texture_height <<= 1;
}
printf(" RoQ_INFO: dimensions = %dx%d, %dx%d; %d mbs, texture = %dx%d\n",
state.width, state.height, state.mb_width, state.mb_height,
state.mb_count, state.stride, state.texture_height);
state.frame[0] = (unsigned short*)malloc(state.texture_height * state.stride * sizeof(unsigned short));
state.frame[1] = (unsigned short*)malloc(state.texture_height * state.stride * sizeof(unsigned short));
state.current_frame = 0;
if (!state.frame[0] || !state.frame[1])
{
free (state.frame[0]);
free (state.frame[1]);
status = ROQ_NO_MEMORY;
break;
}
memset(state.frame[0], 0, state.texture_height * state.stride * sizeof(unsigned short));
memset(state.frame[1], 0, state.texture_height * state.stride * sizeof(unsigned short));
/* set this flag so that this code is not executed again when
* looping */
initialized = 1;
break;
case RoQ_QUAD_CODEBOOK:
status = roq_unpack_quad_codebook(read_buffer, chunk_size,
chunk_arg, &state);
break;
case RoQ_QUAD_VQ:
status = roq_unpack_vq(read_buffer, chunk_size,
chunk_arg, &state);
if (render_cb)
status = render_cb(state.frame[state.current_frame],
state.width, state.height, state.stride, state.texture_height);
break;
case RoQ_SOUND_MONO:
roq_audio.channels = 1;
roq_audio.pcm_samples = chunk_size*2;
snd_left = chunk_arg;
for(i = 0; i < chunk_size; i++)
{
snd_left += roq_audio.snd_sqr_arr[read_buffer[i]];
roq_audio.pcm_sample[i * 2] = snd_left & 0xff;
roq_audio.pcm_sample[i * 2 + 1] = (snd_left & 0xff00) >> 8;
}
if (audio_cb)
status = audio_cb( roq_audio.pcm_sample, roq_audio.pcm_samples,
roq_audio.channels );
break;
case RoQ_SOUND_STEREO:
roq_audio.channels = 2;
roq_audio.pcm_samples = chunk_size*2;
snd_left = (chunk_arg & 0xFF00);
snd_right = (chunk_arg & 0xFF) << 8;
for(i = 0; i < chunk_size; i += 2)
{
snd_left += roq_audio.snd_sqr_arr[read_buffer[i]];
snd_right += roq_audio.snd_sqr_arr[read_buffer[i+1]];
roq_audio.pcm_sample[i * 2] = snd_left & 0xff;
roq_audio.pcm_sample[i * 2 + 1] = (snd_left & 0xff00) >> 8;
roq_audio.pcm_sample[i * 2 + 2] = snd_right & 0xff;
roq_audio.pcm_sample[i * 2 + 3] = (snd_right & 0xff00) >> 8;
}
if (audio_cb)
status = audio_cb( roq_audio.pcm_sample, roq_audio.pcm_samples,
roq_audio.channels );
break;
default:
break;
}
}
free(state.frame[0]);
free(state.frame[1]);
fs_close(f);
return status;
}

View File

@ -0,0 +1,37 @@
/*
* Dreamroq by Mike Melanson
*
* This is the header file to be included in the programs wishing to
* use the Dreamroq playback engine.
*/
#ifndef NEWROQ_H
#define NEWROQ_H
#define ROQ_SUCCESS 0
#define ROQ_FILE_OPEN_FAILURE 1
#define ROQ_FILE_READ_FAILURE 2
#define ROQ_CHUNK_TOO_LARGE 3
#define ROQ_BAD_CODEBOOK 4
#define ROQ_INVALID_PIC_SIZE 5
#define ROQ_NO_MEMORY 6
#define ROQ_BAD_VQ_STREAM 7
#define ROQ_INVALID_DIMENSION 8
#define ROQ_RENDER_PROBLEM 9
#define ROQ_CLIENT_PROBLEM 10
/* The library calls this function when it has a frame ready for display. */
typedef int (*render_callback)(unsigned short *buf, int width, int height,
int stride, int texture_height);
/* The library calls this function when it has pcm samples ready for output. */
typedef int (*audio_callback)(unsigned char *buf, int samples, int channels);
/* The library calls this function to ask whether it should quit playback.
* Return non-zero if it's time to quite. */
typedef int (*quit_callback)();
int dreamroq_play(char *filename, int loop, render_callback render_cb,
audio_callback audio_cb, quit_callback quit_cb);
#endif /* NEWROQ_H */

View File

@ -0,0 +1,138 @@
/* KallistiOS ##version##
aica_cmd_iface.h
(c)2000-2002 Dan Potter
Definitions for the SH-4/AICA interface. This file is meant to be
included from both the ARM and SH-4 sides of the fence.
*/
#ifndef __ARM_AICA_CMD_IFACE_H
#define __ARM_AICA_CMD_IFACE_H
/* $Id: aica_cmd_iface.h,v 1.3 2002/06/13 05:52:35 bardtx Exp $ */
#ifndef __ARCH_TYPES_H
typedef unsigned long uint8;
typedef unsigned long uint32;
#endif
/* Command queue; one of these for passing data from the SH-4 to the
AICA, and another for the other direction. If a command is written
to the queue and it is longer than the amount of space between the
head point and the queue size, the command will wrap around to
the beginning (i.e., queue commands _can_ be split up). */
typedef struct aica_queue {
uint32 head; /* Insertion point offset (in bytes) */
uint32 tail; /* Removal point offset (in bytes) */
uint32 size; /* Queue size (in bytes) */
uint32 valid; /* 1 if the queue structs are valid */
uint32 process_ok; /* 1 if it's ok to process the data */
uint32 data; /* Pointer to queue data buffer */
} aica_queue_t;
/* Command queue struct for commanding the AICA from the SH-4 */
typedef struct aica_cmd {
uint32 size; /* Command data size in dwords */
uint32 cmd; /* Command ID */
uint32 timestamp; /* When to execute the command (0 == now) */
uint32 cmd_id; /* Command ID, for cmd/response pairs, or channel id */
uint32 misc[4]; /* Misc Parameters / Padding */
uint8 cmd_data[0]; /* Command data */
} aica_cmd_t;
/* Maximum command size -- 256 dwords */
#define AICA_CMD_MAX_SIZE 256
/* This is the cmd_data for AICA_CMD_CHAN. Make this 16 dwords long
for two aica bus queues. */
typedef struct aica_channel {
uint32 cmd; /* Command ID */
uint32 base; /* Sample base in RAM */
uint32 type; /* (8/16bit/ADPCM) */
uint32 length; /* Sample length */
uint32 loop; /* Sample looping */
uint32 loopstart; /* Sample loop start */
uint32 loopend; /* Sample loop end */
uint32 freq; /* Frequency */
uint32 vol; /* Volume 0-255 */
uint32 pan; /* Pan 0-255 */
uint32 pos; /* Sample playback pos */
uint32 pad[5]; /* Padding */
} aica_channel_t;
/* Declare an aica_cmd_t big enough to hold an aica_channel_t
using temp name T, aica_cmd_t name CMDR, and aica_channel_t name CHANR */
#define AICA_CMDSTR_CHANNEL(T, CMDR, CHANR) \
uint8 T[sizeof(aica_cmd_t) + sizeof(aica_channel_t)]; \
aica_cmd_t * CMDR = (aica_cmd_t *)T; \
aica_channel_t * CHANR = (aica_channel_t *)(CMDR->cmd_data);
#define AICA_CMDSTR_CHANNEL_SIZE ((sizeof(aica_cmd_t) + sizeof(aica_channel_t))/4)
/* Command values (for aica_cmd_t) */
#define AICA_CMD_NONE 0x00000000 /* No command (dummy packet) */
#define AICA_CMD_PING 0x00000001 /* Check for signs of life */
#define AICA_CMD_CHAN 0x00000002 /* Perform a wavetable action */
#define AICA_CMD_SYNC_CLOCK 0x00000003 /* Reset the millisecond clock */
/* Response values (for aica_cmd_t) */
#define AICA_RESP_NONE 0x00000000
#define AICA_RESP_PONG 0x00000001 /* Response to CMD_PING */
#define AICA_RESP_DBGPRINT 0x00000002 /* Entire payload is a null-terminated string */
/* Command values (for aica_channel_t commands) */
#define AICA_CH_CMD_MASK 0x0000000f
#define AICA_CH_CMD_NONE 0x00000000
#define AICA_CH_CMD_START 0x00000001
#define AICA_CH_CMD_STOP 0x00000002
#define AICA_CH_CMD_UPDATE 0x00000003
/* Start values */
#define AICA_CH_START_MASK 0x00300000
#define AICA_CH_START_DELAY 0x00100000 /* Set params, but delay key-on */
#define AICA_CH_START_SYNC 0x00200000 /* Set key-on for all selected channels */
/* Update values */
#define AICA_CH_UPDATE_MASK 0x000ff000
#define AICA_CH_UPDATE_SET_FREQ 0x00001000 /* frequency */
#define AICA_CH_UPDATE_SET_VOL 0x00002000 /* volume */
#define AICA_CH_UPDATE_SET_PAN 0x00004000 /* panning */
/* Sample types */
#define AICA_SM_8BIT 1
#define AICA_SM_16BIT 0
#define AICA_SM_ADPCM 2
/* This is where our SH-4/AICA comm variables go... */
/* 0x000000 - 0x010000 are reserved for the program */
/* Location of the SH-4 to AICA queue; commands from here will be
periodically processed by the AICA and then removed from the queue. */
#define AICA_MEM_CMD_QUEUE 0x010000 /* 32K */
/* Location of the AICA to SH-4 queue; commands from here will be
periodically processed by the SH-4 and then removed from the queue. */
#define AICA_MEM_RESP_QUEUE 0x018000 /* 32K */
/* This is the channel base, which holds status structs for all the
channels. This is READ-ONLY from the SH-4 side. */
#define AICA_MEM_CHANNELS 0x020000 /* 64 * 16*4 = 4K */
/* The clock value (in milliseconds) */
#define AICA_MEM_CLOCK 0x021000 /* 4 bytes */
/* 0x021004 - 0x030000 are reserved for future expansion */
/* Open ram for sample data */
#define AICA_RAM_START 0x030000
#define AICA_RAM_END 0x200000
/* Quick access to the AICA channels */
#define AICA_CHANNEL(x) (AICA_MEM_CHANNELS + (x) * sizeof(aica_channel_t))
#endif /* __ARM_AICA_CMD_IFACE_H */

View File

@ -0,0 +1,19 @@
/*
**
** This File is a part of Dreamcast Media Center
** (C) Josh "PH3NOM" Pearson 2011
**
*/
/* 'Public' Function Protocols */
#ifndef DCTIMER_H
#define DCTIMER_H
/* Returns hardware time in miliseconds */
int dc_get_time();
/* Regulate the Video Frame Rate */
void frame_delay( float AVI_video_rate, float AVI_delay, int frameCounter );
#endif

View File

@ -0,0 +1,532 @@
/*
**
** Josh 'PH3NOM' Pearson 2011
** Notes: Had to modify the requested samples by soundstream_poll
** for easy integration with libROQ
*/
/* KallistiOS ##version##
snd_stream.c
Copyright (c)2000,2001,2002,2003,2004 Dan Potter
Copyright (c)2002 Florian Schulze
SH-4 support routines for SPU streaming sound driver
*/
/* Missing headers Ian micheal 2020*/
/*
Name: Ian micheal
Copyright:
Author: Ian micheal
Date: 12/08/23 05:17
Description: kos 2.0 up port threading fix and wrappers and all warnings fixed
*/
#include <kos.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/queue.h>
#include <arch/timer.h>
#include <dc/g2bus.h>
#include <dc/spu.h>
#include <dc/sound/sound.h>
#include <dc/sound/stream.h>
#include <dc/sound/sfxmgr.h>
#include "arm/aica_cmd_iface.h"
/*
This module uses a nice circularly queued data stream in SPU RAM, which is
looped by a program running in the SPU itself.
Basically the poll routine checks to see if a certain minimum amount of
data is available to the SPU to be played, and if not, we ask the user
routine for more sound data and load it up. That's about it.
This version is capable of playing back N streams at once, with the limit
being available CPU time and channels.
*/
typedef struct filter {
TAILQ_ENTRY(filter) lent;
snd_stream_filter_t func;
void * data;
} filter_t;
/* Each of these represents an active streaming channel */
typedef struct strchan {
// Which AICA channels are we using?
int ch[2];
// The last write position in the playing buffer
int last_write_pos; // = 0
int curbuffer; // = 0
// The buffer size allocated for this stream.
int buffer_size; // = 0x10000
// Stream data location in AICA RAM
uint32 spu_ram_sch[2];
// "Get data" callback; we'll call this any time we want to get
// another buffer of output data.
snd_stream_callback_t get_data;
// Our list of filter callback functions for this stream
TAILQ_HEAD(filterlist, filter) filters;
// Stereo/mono flag
int stereo;
// Playback frequency
int frequency;
/* Stream queueing is where we get everything ready to go but don't
actually start it playing until the signal (for music sync, etc) */
int queueing;
/* Have we been initialized yet? (and reserved a buffer, etc) */
volatile int initted;
} strchan_t;
// Our stream structs
static strchan_t streams[SND_STREAM_MAX] = { { { 0 } } };
// Separation buffers (for stereo)
int16 * sep_buffer[2] = { NULL, NULL };
/* the address of the sound ram from the SH4 side */
#define SPU_RAM_BASE 0xa0800000
// Check an incoming handle
#define CHECK_HND(x) do { \
assert( (x) >= 0 && (x) < SND_STREAM_MAX ); \
assert( streams[(x)].initted ); \
} while(0)
/* Set "get data" callback */
void snd_stream_set_callback(snd_stream_hnd_t hnd, snd_stream_callback_t cb) {
CHECK_HND(hnd);
streams[hnd].get_data = cb;
}
void snd_stream_filter_add(snd_stream_hnd_t hnd, snd_stream_filter_t filtfunc, void * obj) {
filter_t * f;
CHECK_HND(hnd);
f = malloc(sizeof(filter_t));
f->func = filtfunc;
f->data = obj;
TAILQ_INSERT_TAIL(&streams[hnd].filters, f, lent);
}
void snd_stream_filter_remove(snd_stream_hnd_t hnd, snd_stream_filter_t filtfunc, void * obj) {
filter_t * f;
CHECK_HND(hnd);
TAILQ_FOREACH(f, &streams[hnd].filters, lent) {
if (f->func == filtfunc && f->data == obj) {
TAILQ_REMOVE(&streams[hnd].filters, f, lent);
free(f);
return;
}
}
}
static void process_filters(snd_stream_hnd_t hnd, void **buffer, int *samplecnt) {
filter_t * f;
TAILQ_FOREACH(f, &streams[hnd].filters, lent) {
f->func(hnd, f->data, streams[hnd].frequency, streams[hnd].stereo ? 2 : 1, buffer, samplecnt);
}
}
/* Performs stereo seperation for the two channels; this routine
has been optimized for the SH-4. */
static void sep_data(void *buffer, int len, int stereo) {
register int16 *bufsrc, *bufdst;
register int x, y, cnt;
if (stereo) {
bufsrc = (int16*)buffer;
bufdst = sep_buffer[0];
x = 0; y = 0; cnt = len / 2;
do {
*bufdst = *bufsrc;
bufdst++; bufsrc+=2; cnt--;
} while (cnt > 0);
bufsrc = (int16*)buffer; bufsrc++;
bufdst = sep_buffer[1];
x = 1; y = 0; cnt = len / 2;
do {
*bufdst = *bufsrc;
bufdst++; bufsrc+=2; cnt--;
x+=2; y++;
} while (cnt > 0);
} else {
memcpy(sep_buffer[0], buffer, len);
memcpy(sep_buffer[1], buffer, len);
}
}
/* Prefill buffers -- do this before calling start() */
void snd_stream_prefill(snd_stream_hnd_t hnd) {
void *buf;
int got;
CHECK_HND(hnd);
if (!streams[hnd].get_data) return;
/* Load first buffer */
/* XXX Note: This will not work if the full data size is less than
buffer_size or buffer_size/2. */
if (streams[hnd].stereo)
buf = streams[hnd].get_data(hnd, streams[hnd].buffer_size, &got);
else
buf = streams[hnd].get_data(hnd, streams[hnd].buffer_size/2, &got);
process_filters(hnd, &buf, &got);
sep_data(buf, (streams[hnd].buffer_size/2), streams[hnd].stereo);
spu_memload(
streams[hnd].spu_ram_sch[0] + (streams[hnd].buffer_size/2)*0,
(uint8*)sep_buffer[0], streams[hnd].buffer_size/2);
spu_memload(
streams[hnd].spu_ram_sch[1] + (streams[hnd].buffer_size/2)*0,
(uint8*)sep_buffer[1], streams[hnd].buffer_size/2);
/* Load second buffer */
if (streams[hnd].stereo)
buf = streams[hnd].get_data(hnd, streams[hnd].buffer_size, &got);
else
buf = streams[hnd].get_data(hnd, streams[hnd].buffer_size/2, &got);
process_filters(hnd, &buf, &got);
sep_data(buf, (streams[hnd].buffer_size/2), streams[hnd].stereo);
spu_memload(
streams[hnd].spu_ram_sch[0] + (streams[hnd].buffer_size/2)*1,
(uint8*)sep_buffer[0], streams[hnd].buffer_size/2);
spu_memload(
streams[hnd].spu_ram_sch[1] + (streams[hnd].buffer_size/2)*1,
(uint8*)sep_buffer[1], streams[hnd].buffer_size/2);
/* Start with playing on buffer 0 */
streams[hnd].last_write_pos = 0;
streams[hnd].curbuffer = 0;
}
/* Initialize stream system */
int snd_stream_init() {
/* Create stereo seperation buffers */
if (!sep_buffer[0]) {
sep_buffer[0] = memalign(32, (SND_STREAM_BUFFER_MAX/2));
sep_buffer[1] = memalign(32, (SND_STREAM_BUFFER_MAX/2));
}
/* Finish loading the stream driver */
if (snd_init() < 0) {
dbglog(DBG_ERROR, "snd_stream_init(): snd_init() failed, giving up\n");
return -1;
}
return 0;
}
snd_stream_hnd_t snd_stream_alloc(snd_stream_callback_t cb, int bufsize) {
int i, old;
snd_stream_hnd_t hnd;
// Get an unused handle
hnd = -1;
old = irq_disable();
for (i=0; i<SND_STREAM_MAX; i++) {
if (!streams[i].initted) {
hnd = i;
break;
}
}
if (hnd != -1)
streams[hnd].initted = 1;
irq_restore(old);
if (hnd == -1)
return SND_STREAM_INVALID;
// Default this for now
streams[hnd].buffer_size = bufsize;
/* Start off with queueing disabled */
streams[hnd].queueing = 0;
/* Setup the callback */
snd_stream_set_callback(hnd, cb);
/* Initialize our filter chain list */
TAILQ_INIT(&streams[hnd].filters);
// Allocate stream buffers
streams[hnd].spu_ram_sch[0] = snd_mem_malloc(streams[hnd].buffer_size*2);
streams[hnd].spu_ram_sch[1] = streams[hnd].spu_ram_sch[0] + streams[hnd].buffer_size;
// And channels
streams[hnd].ch[0] = snd_sfx_chn_alloc();
streams[hnd].ch[1] = snd_sfx_chn_alloc();
printf("snd_stream: alloc'd channels %d/%d\n", streams[hnd].ch[0], streams[hnd].ch[1]);
return hnd;
}
int snd_stream_reinit(snd_stream_hnd_t hnd, snd_stream_callback_t cb) {
CHECK_HND(hnd);
/* Start off with queueing disabled */
streams[hnd].queueing = 0;
/* Setup the callback */
snd_stream_set_callback(hnd, cb);
return hnd;
}
void snd_stream_destroy(snd_stream_hnd_t hnd) {
filter_t * c, * n;
CHECK_HND(hnd);
if (!streams[hnd].initted)
return;
snd_sfx_chn_free(streams[hnd].ch[0]);
snd_sfx_chn_free(streams[hnd].ch[1]);
c = TAILQ_FIRST(&streams[hnd].filters);
while (c) {
n = TAILQ_NEXT(c, lent);
free(c);
c = n;
}
TAILQ_INIT(&streams[hnd].filters);
snd_stream_stop(hnd);
snd_mem_free(streams[hnd].spu_ram_sch[0]);
memset(streams+hnd, 0, sizeof(streams[0]));
}
/* Shut everything down and free mem */
void snd_stream_shutdown() {
/* Stop and destroy all active stream */
int i;
for (i=0; i<SND_STREAM_MAX; i++) {
if (streams[i].initted)
snd_stream_destroy(i);
}
/* Free global buffers */
if (sep_buffer[0]) {
free(sep_buffer[0]); sep_buffer[0] = NULL;
free(sep_buffer[1]); sep_buffer[1] = NULL;
}
}
/* Enable / disable stream queueing */
void snd_stream_queue_enable(snd_stream_hnd_t hnd) {
CHECK_HND(hnd);
streams[hnd].queueing = 1;
}
void snd_stream_queue_disable(snd_stream_hnd_t hnd) {
CHECK_HND(hnd);
streams[hnd].queueing = 0;
}
/* Start streaming (or if queueing is enabled, just get ready) */
void snd_stream_start(snd_stream_hnd_t hnd, uint32 freq, int st) {
AICA_CMDSTR_CHANNEL(tmp, cmd, chan);
CHECK_HND(hnd);
if (!streams[hnd].get_data) return;
streams[hnd].stereo = st;
streams[hnd].frequency = freq;
/* Make sure these are sync'd (and/or delayed) */
snd_sh4_to_aica_stop();
/* Prefill buffers */
snd_stream_prefill(hnd);
/* Channel 0 */
cmd->cmd = AICA_CMD_CHAN;
cmd->timestamp = 0;
cmd->size = AICA_CMDSTR_CHANNEL_SIZE;
cmd->cmd_id = streams[hnd].ch[0];
chan->cmd = AICA_CH_CMD_START | AICA_CH_START_DELAY;
chan->base = streams[hnd].spu_ram_sch[0];
chan->type = AICA_SM_16BIT;
chan->length = (streams[hnd].buffer_size/2);
chan->loop = 1;
chan->loopstart = 0;
chan->loopend = (streams[hnd].buffer_size/2);
chan->freq = freq;
chan->vol = 255;
chan->pan = 0;
snd_sh4_to_aica(tmp, cmd->size);
/* Channel 1 */
cmd->cmd_id = streams[hnd].ch[1];
chan->base = streams[hnd].spu_ram_sch[1];
chan->pan = 255;
snd_sh4_to_aica(tmp, cmd->size);
/* Start both channels simultaneously */
cmd->cmd_id = (1 << streams[hnd].ch[0]) |
(1 << streams[hnd].ch[1]);
chan->cmd = AICA_CH_CMD_START | AICA_CH_START_SYNC;
snd_sh4_to_aica(tmp, cmd->size);
/* Process the changes */
if (!streams[hnd].queueing)
snd_sh4_to_aica_start();
}
/* Actually make it go (in queued mode) */
void snd_stream_queue_go(snd_stream_hnd_t hnd) {
CHECK_HND(hnd);
snd_sh4_to_aica_start();
}
/* Stop streaming */
void snd_stream_stop(snd_stream_hnd_t hnd) {
AICA_CMDSTR_CHANNEL(tmp, cmd, chan);
CHECK_HND(hnd);
if (!streams[hnd].get_data) return;
/* Stop stream */
/* Channel 0 */
cmd->cmd = AICA_CMD_CHAN;
cmd->timestamp = 0;
cmd->size = AICA_CMDSTR_CHANNEL_SIZE;
cmd->cmd_id = streams[hnd].ch[0];
chan->cmd = AICA_CH_CMD_STOP;
snd_sh4_to_aica(tmp, cmd->size);
/* Channel 1 */
cmd->cmd_id = streams[hnd].ch[1];
snd_sh4_to_aica(tmp, AICA_CMDSTR_CHANNEL_SIZE);
}
/* The DMA will chain to this to start the second DMA. */
/* static uint32 dmadest, dmacnt;
static void dma_chain(ptr_t data) {
spu_dma_transfer(sep_buffer[1], dmadest, dmacnt, 0, NULL, 0);
} */
/* Poll streamer to load more data if necessary */
int snd_stream_poll(snd_stream_hnd_t hnd) {
uint32 ch0pos, ch1pos;
// Ian micheal fixed all threading problems
/* int realbuffer; */ // Remove this line
int current_play_pos;
int needed_samples;
int got_samples;
void *data;
CHECK_HND(hnd);
if (!streams[hnd].get_data) return -1;
// Ian micheal fixed all threading problems
/* Get "real" buffer */
ch0pos = g2_read_32(SPU_RAM_BASE + AICA_CHANNEL(streams[hnd].ch[0]) + offsetof(aica_channel_t, pos));
ch1pos = g2_read_32(SPU_RAM_BASE + AICA_CHANNEL(streams[hnd].ch[1]) + offsetof(aica_channel_t, pos));
if (ch0pos >= (streams[hnd].buffer_size/2)) {
dbglog(DBG_ERROR, "snd_stream_poll: chan0(%d).pos = %ld (%08lx)\n", streams[hnd].ch[0], ch0pos, ch0pos);
return -1;
}
current_play_pos = (ch0pos < ch1pos)?(ch0pos):(ch1pos);
/* count just till the end of the buffer, so we don't have to
handle buffer wraps */
if (streams[hnd].last_write_pos <= current_play_pos)
needed_samples = current_play_pos - streams[hnd].last_write_pos;
else
needed_samples = (streams[hnd].buffer_size/2) - streams[hnd].last_write_pos;
/* round it a little bit */
needed_samples &= ~0x7ff;
/* printf("last_write_pos %6i, current_play_pos %6i, needed_samples %6i\n",last_write_pos,current_play_pos,needed_samples); */
//if (needed_samples > 0) {
if (needed_samples ==4096) {
if (streams[hnd].stereo) {
data = streams[hnd].get_data(hnd, needed_samples * 4, &got_samples);
process_filters(hnd, &data, &got_samples);
if (got_samples < needed_samples * 4) {
needed_samples = got_samples / 4;
if (needed_samples & 3)
needed_samples = (needed_samples + 4) & ~3;
}
} else {
data = streams[hnd].get_data(hnd, needed_samples * 2, &got_samples);
process_filters(hnd, &data, &got_samples);
if (got_samples < needed_samples * 2) {
needed_samples = got_samples / 2;
if (needed_samples & 1)
needed_samples = (needed_samples + 2) & ~1;
}
}
if (data == NULL) {
/* Fill the "other" buffer with zeros */
spu_memset(streams[hnd].spu_ram_sch[0] + (streams[hnd].last_write_pos * 2), 0, needed_samples * 2);
spu_memset(streams[hnd].spu_ram_sch[1] + (streams[hnd].last_write_pos * 2), 0, needed_samples * 2);
return -3;
}
sep_data(data, needed_samples * 2, streams[hnd].stereo);
spu_memload(streams[hnd].spu_ram_sch[0] + (streams[hnd].last_write_pos * 2), (uint8*)sep_buffer[0], needed_samples * 2);
spu_memload(streams[hnd].spu_ram_sch[1] + (streams[hnd].last_write_pos * 2), (uint8*)sep_buffer[1], needed_samples * 2);
// Second DMA will get started by the chain handler
/* dcache_flush_range(sep_buffer[0], needed_samples*2);
dcache_flush_range(sep_buffer[1], needed_samples*2);
dmadest = spu_ram_sch2 + (last_write_pos * 2);
dmacnt = needed_samples * 2;
spu_dma_transfer(sep_buffer[0], spu_ram_sch1 + (last_write_pos * 2), needed_samples * 2,
0, dma_chain, 0); */
streams[hnd].last_write_pos += needed_samples;
if (streams[hnd].last_write_pos >= (streams[hnd].buffer_size/2))
streams[hnd].last_write_pos -= (streams[hnd].buffer_size/2);
}
return 0;
}
/* Set the volume on the streaming channels */
void snd_stream_volume(snd_stream_hnd_t hnd, int vol) {
AICA_CMDSTR_CHANNEL(tmp, cmd, chan);
CHECK_HND(hnd);
cmd->cmd = AICA_CMD_CHAN;
cmd->timestamp = 0;
cmd->size = AICA_CMDSTR_CHANNEL_SIZE;
cmd->cmd_id = streams[hnd].ch[0];
chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_VOL;
chan->vol = vol;
snd_sh4_to_aica(tmp, cmd->size);
cmd->cmd_id = streams[hnd].ch[1];
snd_sh4_to_aica(tmp, cmd->size);
}

View File

@ -0,0 +1,142 @@
/*
**
** (C) Josh 'PH3NOM' Pearson 2011
**
*/
/*
** To anyone looking at this code:
**
** This driver runs in its own thread on the sh4.
**
** When the AICA driver requests more samples,
** it will signal sndbuf_status=SNDDRV_STATUS_NEEDBUF
** and assign the number of requested samples to snddrv.pcm_needed.
**
** The decoders need to check sndbuf_status,
** when more samples are requested by the driver ** the decoders will loop
** decoding into pcm_buffer untill pcm_bytes==snddrv.pcm_needed
** at that point the decoder signals sndbuf_status=SNDDRV_STATUS_HAVEBUF
**
*/
#include <kos/thread.h>
#include <dc/sound/stream.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "snddrv.h"
snd_stream_hnd_t shnd;
kthread_t * snddrv_thd;
static int snddrv_vol = 255;
snd_drv snddrv;
/* Increase the Sound Driver volume */
int snddrv_volume_up() {
if( snddrv_vol <= 245 ) {
snddrv_vol += 10;
snd_stream_volume(shnd, snddrv_vol);
}
return snddrv_vol;
}
/* Decrease the Sound Driver volume */
int snddrv_volume_down() {
if( snddrv_vol >= 10 ) {
snddrv_vol -= 10;
snd_stream_volume(shnd, snddrv_vol);
}
return snddrv_vol;
}
/* Exit the Sound Driver */
int snddrv_exit() {
if( snddrv.drv_status != SNDDRV_STATUS_NULL ) {
snddrv.drv_status = SNDDRV_STATUS_DONE;
snddrv.buf_status = SNDDRV_STATUS_BUFEND;
while( snddrv.drv_status != SNDDRV_STATUS_NULL )
thd_pass();
printf("SNDDRV: Exited\n");
}
memset( snddrv.pcm_buffer, 0, 65536+16384);
snddrv.pcm_bytes = 0;
snddrv.pcm_needed = 0;
SNDDRV_FREE_STRUCT();
return snddrv.drv_status;
}
/* Signal how many samples the AICA needs, then wait for the deocder to produce them */
static void *snddrv_callback(snd_stream_hnd_t hnd, int len, int * actual) {
/* Signal the Decoder thread how many more samples are needed */
snddrv.pcm_needed = len;
snddrv.buf_status = SNDDRV_STATUS_NEEDBUF;
/* Wait for the samples to be ready */
while( snddrv.buf_status != SNDDRV_STATUS_HAVEBUF && snddrv.buf_status != SNDDRV_STATUS_BUFEND )
thd_pass();
snddrv.pcm_ptr = snddrv.pcm_buffer;
snddrv.pcm_bytes = 0;
*actual = len;
return snddrv.pcm_ptr;
}
static int snddrv_thread() {
printf("SNDDRV: Rate - %i, Channels - %i\n", snddrv.rate, snddrv.channels);
shnd = snd_stream_alloc(snddrv_callback, SND_STREAM_BUFFER_MAX/4);
snd_stream_start(shnd, snddrv.rate, snddrv.channels-1);
snddrv.drv_status = SNDDRV_STATUS_STREAMING;
while( snddrv.drv_status != SNDDRV_STATUS_DONE && snddrv.drv_status != SNDDRV_STATUS_ERROR ) {
snd_stream_poll(shnd);
thd_sleep(20);
}
snddrv.drv_status = SNDDRV_STATUS_NULL;
snd_stream_destroy(shnd);
snd_stream_shutdown();
printf("SNDDRV: Finished\n");
return snddrv.drv_status;
}
/* Start the AICA Sound Stream Thread */
int snddrv_start( int rate, int chans ) {
snddrv.rate = rate;
snddrv.channels = chans;
if( snddrv.channels > 2) {
printf("SNDDRV: ERROR - Exceeds maximum channels\n");
return -1;
}
printf("SNDDRV: Creating Driver Thread\n");
snddrv.drv_status = SNDDRV_STATUS_INITIALIZING;
snd_stream_init();
/*libdcmc/snddrv.c:136: warning: passing arg 1 of `thd_create' from incompatible pointer type */ //Ian micheal 2020 warning
snddrv_thd = thd_create(0, snddrv_thread, NULL );
return snddrv.drv_status;
}

View File

@ -0,0 +1,110 @@
/*
**
** This File is a part of Dreamcast Media Center
** (C) Josh "PH3NOM" Pearson 2011
**
*/
#ifndef SNDDRV_H
#define SNDDRV_H
/* Keep track of things from the Driver side */
#define SNDDRV_STATUS_NULL 0x00
#define SNDDRV_STATUS_INITIALIZING 0x01
#define SNDDRV_STATUS_READY 0x02
#define SNDDRV_STATUS_STREAMING 0x03
#define SNDDRV_STATUS_DONE 0x04
#define SNDDRV_STATUS_ERROR 0x05
/* Keep track of things from the Decoder side */
#define SNDDEC_STATUS_NULL 0x00
#define SNDDEC_STATUS_INITIALIZING 0x01
#define SNDDEC_STATUS_READY 0x02
#define SNDDEC_STATUS_STREAMING 0x03
#define SNDDEC_STATUS_PAUSING 0x04
#define SNDDEC_STATUS_PAUSED 0x05
#define SNDDEC_STATUS_RESUMING 0x06
#define SNDDEC_STATUS_DONE 0x07
#define SNDDEC_STATUS_ERROR 0x08
/* Keep track of the buffer status from both sides*/
#define SNDDRV_STATUS_NEEDBUF 0x00
#define SNDDRV_STATUS_HAVEBUF 0x01
#define SNDDRV_STATUS_BUFEND 0x02
/* This seems to be a good number for file seeking on compressed audio */
#define SEEK_LEN 16384*48
/* SNDDRV (C) AICA Audio Driver */
typedef struct _snd_drv {
int rate;
int channels;
int pcm_bytes;
int pcm_needed;
volatile int drv_status;
volatile int dec_status;
volatile int buf_status;
unsigned int pcm_buffer[65536+16384];
unsigned int *pcm_ptr;
} snd_drv;
extern snd_drv snddrv;
#define SNDDRV_FREE_STRUCT() { \
snddrv.rate = snddrv.channels = snddrv.drv_status = \
snddrv.dec_status = snddrv.buf_status = 0; }
typedef struct snddrv_song_info {
char *artist[128];
char * title[128];
char * track[128];
char * album[128];
char * genre[128];
char *fname;
volatile int fpos;
volatile float spos;
int fsize;
float slen;
} snd_sinfo;
#define SNDDRV_FREE_SINFO() { \
sq_clr( snd_sinfo.artist, 128 ); \
sq_clr( snd_sinfo.title, 128 ); \
sq_clr( snd_sinfo.track, 128 ); \
sq_clr( snd_sinfo.album, 128 ); \
sq_clr( snd_sinfo.genre, 128 ); \
snd_sinfo.fpos = snd_sinfo.spos = snd_sinfo.fsize = snd_sinfo.slen = 0; }
#define min(a,b) ( (a) < (b) ? (a) : (b) )
#define MAX_CHANNELS 6 /* make this higher to support files with
more channels for LibFAAD */
/* MicroSoft channel definitions */
#define SPEAKER_FRONT_LEFT 0x1
#define SPEAKER_FRONT_RIGHT 0x2
#define SPEAKER_FRONT_CENTER 0x4
#define SPEAKER_LOW_FREQUENCY 0x8
#define SPEAKER_BACK_LEFT 0x10
#define SPEAKER_BACK_RIGHT 0x20
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80
#define SPEAKER_BACK_CENTER 0x100
#define SPEAKER_SIDE_LEFT 0x200
#define SPEAKER_SIDE_RIGHT 0x400
#define SPEAKER_TOP_CENTER 0x800
#define SPEAKER_TOP_FRONT_LEFT 0x1000
#define SPEAKER_TOP_FRONT_CENTER 0x2000
#define SPEAKER_TOP_FRONT_RIGHT 0x4000
#define SPEAKER_TOP_BACK_LEFT 0x8000
#define SPEAKER_TOP_BACK_CENTER 0x10000
#define SPEAKER_TOP_BACK_RIGHT 0x20000
#define SPEAKER_RESERVED 0x80000000
/* SNDDRV Function Protocols */
int snddrv_start( int rate, int chans );
int snddrv_exit();
int snddrv_volume_up();
int snddrv_volume_down();
#endif

View File

@ -0,0 +1,36 @@
/*
**
** This file is a part of Dreamcast Media Center
** (C) Josh PH3NOM Pearson 2011
**
*/
#include <stdio.h>
#include <time.h>
#include <arch/timer.h>
#include <kos/thread.h>
#include "dc_timer.h"
/* Get current hardware timing using arch/timer.h */
int dc_get_time()
{
uint32 s, ms;
uint64 msec;
timer_ms_gettime(&s, &ms);
msec = (((uint64)s) * ((uint64)1000)) + ((uint64)ms);
return (int)msec;
}
/* Regulate the Video Frame Rate */
void frame_delay( float AVI_video_rate, float AVI_delay, int frameCounter )
{
float AVI_real_time = frameCounter / AVI_video_rate;
float CPU_real_time= ( ( (float)dc_get_time()- AVI_delay ) / 1000.0f );
//printf("AVI_real_time: %f, CPU_real_time %f\n", AVI_real_time, CPU_real_time );
while ( CPU_real_time < AVI_real_time ) {
CPU_real_time= ( ( (float)dc_get_time()- AVI_delay ) / 1000.0f );
thd_pass();
}
}

272
samples/dreamroq/main.c Normal file
View File

@ -0,0 +1,272 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glu.h>
#include <GL/glkos.h>
#include <kos.h>
#include <dc/pvr.h>
#include <dc/maple.h>
#include <dc/maple/controller.h>
#include <kos/mutex.h>
#include <kos/thread.h>
#include "libdcmc/snddrv.h"
#include "dreamroqlib.h"
#include "libdcmc/dc_timer.h"
#ifdef __DREAMCAST__
extern uint8 romdisk[];
KOS_INIT_ROMDISK(romdisk);
#define VIDEO_FILENAME "/rd/video.roq"
#else
#define VIDEO_FILENAME "../samples/dreamroq/romdisk/video.roq"
#endif
maple_device_t *cont;
cont_state_t *state;
/* Audio Global variables */
#define PCM_BUF_SIZE 1024*1024
static unsigned char *pcm_buf = NULL;
static int pcm_size = 0;
static int audio_init = 0;
static mutex_t * pcm_mut;
/* Video Global variables */
static int current_frame = 0;
static int graphics_initialized = 0;
static float video_delay = 0.0f;
static int frame = 0;
static const float VIDEO_RATE = 30.0f; /* Video FPS */
static GLint frameTexture[2];
//static GLVertexKOS vertices[4];
// maybe should aling this better?
typedef struct {
float x, y, z;
} Vec3;
typedef struct {
float u, v;
} UV;
static Vec3 vertices[4];
static UV uv[4];
static void snd_thd()
{
do
{
/* Wait for AICA Driver to request some samples */
while( snddrv.buf_status != SNDDRV_STATUS_NEEDBUF )
thd_pass();
/* Wait for RoQ Decoder to produce enough samples */
while( pcm_size < snddrv.pcm_needed )
{
if( snddrv.dec_status == SNDDEC_STATUS_DONE )
goto done;
thd_pass();
}
/* Copy the Requested PCM Samples to the AICA Driver */
mutex_lock( pcm_mut );
memcpy( snddrv.pcm_buffer, pcm_buf, snddrv.pcm_needed );
/* Shift the Remaining PCM Samples Back */
pcm_size -= snddrv.pcm_needed;
memmove( pcm_buf, pcm_buf+snddrv.pcm_needed, pcm_size );
mutex_unlock( pcm_mut );
/* Let the AICA Driver know the PCM samples are ready */
snddrv.buf_status = SNDDRV_STATUS_HAVEBUF;
} while( snddrv.dec_status == SNDDEC_STATUS_STREAMING );
done:
snddrv.dec_status = SNDDEC_STATUS_NULL;
}
static int renderGLdc_cb(unsigned short *buf, int width, int height, int stride, int texture_height)
{
/* send the video frame as a texture over to video RAM */
//pvr_txr_load(buf, textures[current_frame], stride * texture_height * 2);
glBindTexture(GL_TEXTURE_2D, frameTexture[current_frame]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, buf);
/* Delay the frame to match Frame Rate */
frame_delay(VIDEO_RATE, video_delay, ++frame);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer (3, GL_FLOAT, 0, vertices);
glTexCoordPointer (2, GL_FLOAT, 0, uv);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glKosSwapBuffers();
/*
if (current_frame)
current_frame = 0;
else
current_frame = 1;
*/
return ROQ_SUCCESS;
}
static int audio_cb( unsigned char *buf, int size, int channels)
{
/* Copy the decoded PCM samples to our local PCM buffer */
mutex_lock( pcm_mut );
memcpy( pcm_buf+pcm_size, buf, size);
pcm_size += size;
mutex_unlock( pcm_mut );
return ROQ_SUCCESS;
}
static int quit_cb()
{
/*
state = (cont_state_t *)maple_dev_status(cont);
if(state->buttons & CONT_START)
return 1;
else
return 0;
*/
}
int main(int argc, char **argv)
{
printf("--- DreamRoQ Player for Dreamcast\n");
glKosInit();
//cont = maple_enum_type(0, MAPLE_FUNC_CONTROLLER);
//state = (cont_state_t *)maple_dev_status(cont);
if(!graphics_initialized) {
glClearColor(1.0f, 0.0f, 0.0f, 1.0f); // This Will Clear The Background Color To Black
glClearDepth(1.0); // Enables Clearing Of The Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//glDisable(GL_DEPTH_TEST);
//glEnable(GL_NORMALIZE);
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); // Reset The Projection Matrix
glOrtho(0.0, 640.0, 0.0, 480.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glGenTextures(2, frameTexture);
glBindTexture(GL_TEXTURE_2D, frameTexture[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL);
glBindTexture(GL_TEXTURE_2D, frameTexture[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL);
video_delay = (float)dc_get_time();
float w = 512;
float h = 512;
int v = 0;
vertices[v].x = 0;
vertices[v].y = 0;
vertices[v].z = 0;
uv[v].u = 0.0f;
uv[v].v = 1.0f;
v++;
vertices[v].x = 0;
vertices[v].y = 480;
vertices[v].z = 0;
uv[v].u = 0.0f;
uv[v].v = 0.0f;
v++;
vertices[v].x = 640;
vertices[v].y = 0;
vertices[v].z = 0;
uv[v].u = 1.0f;
uv[v].v = 1.0f;
v++;
vertices[v].x = 640;
vertices[v].y = 480;
vertices[v].z = 0;
uv[v].u = 1.0f;
uv[v].v = 0.0f;
v++;
GLfloat drawColor[4] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat emissionColor[4] = {0.0, 0.0, 0.0, 1.0f};
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, drawColor);
glMaterialfv(GL_FRONT, GL_SPECULAR, drawColor);
glMaterialfv(GL_FRONT, GL_EMISSION, emissionColor);
graphics_initialized = 1;
}
/*
if(!audio_init) {
// allocate PCM buffer
pcm_buf = malloc(PCM_BUF_SIZE);
if( pcm_buf == NULL )
return ROQ_NO_MEMORY;
// Start AICA Driver
// Audio rate, channel number
snddrv_start( 22050, 2);
snddrv.dec_status = SNDDEC_STATUS_STREAMING;
// Create a thread to stream the samples to the AICA
thd_create(0, snd_thd, NULL);
// Create a mutex to handle the double-threaded buffer
//pcm_mut = mutex_create();
audio_init = 1;
}
*/
printf("--- Playing video using DreamRoQ\n");
int status = dreamroq_play(VIDEO_FILENAME, 0, renderGLdc_cb, 0, 0);
printf("dreamroq_play() status = %d\n", status);
/*
if(audio_init) {
snddrv.dec_status = SNDDEC_STATUS_DONE; // Singal audio thread to stop
while( snddrv.dec_status != SNDDEC_STATUS_NULL )
thd_pass();
free( pcm_buf );
pcm_buf = NULL;
pcm_size = 0;
mutex_destroy(pcm_mut); // Destroy the PCM mutex
snddrv_exit(); // Exit the AICA Driver
}
if(graphics_initialized) {
glDeleteTextures(2, frameTexture);
glEnable(GL_LIGHTING);
}
*/
return status;
}

Binary file not shown.