Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pimoroni Pico DV - Raw Audio/Video Player #22

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions software/apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ add_subdirectory(terminal)
add_subdirectory(vista)
add_subdirectory(vista-palette)
add_subdirectory(mandel-full)
add_subdirectory(raw_av_player)
50 changes: 50 additions & 0 deletions software/apps/raw_av_player/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Replace TMDS with 10 bit UART (same baud rate):
# add_definitions(-DDVI_SERIAL_DEBUG=1)
# add_definitions(-DRUN_FROM_CRYSTAL)

include(pimoroni_pico_import.cmake)
include(drivers/fatfs/fatfs OPTIONAL RESULT_VARIABLE INC_FATFS)
include(drivers/sdcard/sdcard OPTIONAL RESULT_VARIABLE INC_SDCARD)

if(DEFINED INC_FATFS AND DEFINED INC_SDCARD)

add_executable(raw_av_player
main.cpp
)

target_compile_options(raw_av_player PRIVATE -Wall -Wno-narrowing)

target_compile_definitions(raw_av_player PRIVATE
DVI_DEFAULT_SERIAL_CONFIG=${DVI_DEFAULT_SERIAL_CONFIG}
SDCARD_PIN_SPI0_CS=22
SDCARD_PIN_SPI0_SCK=5
SDCARD_PIN_SPI0_MOSI=18
SDCARD_PIN_SPI0_MISO=19
SDCARD_PIO=pio1
SDCARD_PIO_SM=0
# compile time configuration of I2S
PICO_AUDIO_I2S_MONO_INPUT=1
#define for our example code
USE_AUDIO_I2S=1
PICO_AUDIO_I2S_DMA_IRQ=1
PICO_AUDIO_I2S_PIO=1
PICO_AUDIO_I2S_DATA=26
PICO_AUDIO_I2S_BCLK=27
)

target_link_libraries(raw_av_player
pico_stdlib
pico_multicore
pico_util
libdvi
libsprite
sdcard
fatfs
pico_audio_i2s
)

# create map/bin/hex file etc.
pico_add_extra_outputs(raw_av_player)
else()
message(WARNING "Directory '${PIMORONI_PICO_PATH}' does not appear to contain the Pimoroni Pico libraries: skipping raw_av_player")
endif()
65 changes: 65 additions & 0 deletions software/apps/raw_av_player/audio.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "pico/audio_i2s.h"

#define SAMPLES_PER_BUFFER 1837


typedef void (*buffer_callback)(int16_t *buffer, uint32_t sample_count);

struct audio_buffer_pool *init_audio(uint32_t sample_rate, uint8_t pin_data, uint8_t pin_bclk, uint8_t pio_sm=0, uint8_t dma_ch=0) {
static audio_format_t audio_format = {
.sample_freq = sample_rate,
.format = AUDIO_BUFFER_FORMAT_PCM_S16,
.channel_count = 1,
};

static struct audio_buffer_format producer_format = {
.format = &audio_format,
.sample_stride = 2
};

struct audio_buffer_pool *producer_pool = audio_new_producer_pool(
&producer_format,
3,
SAMPLES_PER_BUFFER
);

const struct audio_format *output_format;

struct audio_i2s_config config = {
.data_pin = pin_data,
.clock_pin_base = pin_bclk,
.dma_channel = dma_ch,
.pio_sm = pio_sm,
};

output_format = audio_i2s_setup(&audio_format, &config);
if (!output_format) {
panic("PicoAudio: Unable to open audio device.\n");
}

bool status = audio_i2s_connect(producer_pool);
if (!status) {
panic("PicoAudio: Unable to connect to audio device.\n");
}

audio_i2s_set_enabled(true);

return producer_pool;
}

void update_buffer(struct audio_buffer_pool *ap, buffer_callback cb) {
struct audio_buffer *buffer = take_audio_buffer(ap, true);
int16_t *samples = (int16_t *) buffer->buffer->bytes;
/*for (uint i = 0; i < buffer->max_sample_count; i++) {
samples[i] = cb();
}*/
cb(samples, buffer->max_sample_count);
buffer->sample_count = buffer->max_sample_count;
give_audio_buffer(ap, buffer);
}
150 changes: 150 additions & 0 deletions software/apps/raw_av_player/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/clocks.h"
#include "hardware/irq.h"
#include "hardware/sync.h"
#include "hardware/gpio.h"
#include "hardware/vreg.h"
#include "audio.hpp"

extern "C" {
#include "dvi.h"
#include "dvi_serialiser.h"
#include "common_dvi_pin_configs.h"
#include "sprite.h"
}

#include "ff.h"

// DVDD 1.2V (1.1V seems ok too)
#define FRAME_WIDTH 400
#define FRAME_HEIGHT 240
#define VREG_VSEL VREG_VOLTAGE_1_25
#define DVI_TIMING dvi_timing_800x480p_60hz

#define AUDIO_SAMPLE_RATE 22050


#define SW_A 14
#define SW_B 15
#define SW_C 16


FATFS fs;
FIL fil;
FRESULT fr;
FIL afil;

struct dvi_inst dvi0;

uint16_t framebuf[FRAME_WIDTH * FRAME_HEIGHT] = {0};

// Note first two scanlines are pushed before DVI start
volatile uint scanline = 2;

int mount_sd() {
fr = f_mount(&fs, "", 1);
if (fr != FR_OK) {
printf("Failed to mount SD card, error: %d\n", fr);
return 1;
}

FILINFO file;
auto dir = new DIR();
printf("Listing /\n");
f_opendir(dir, "/");
while(f_readdir(dir, &file) == FR_OK && file.fname[0]) {
printf("%s %lld\n", file.fname, file.fsize);
}
f_closedir(dir);

return 0;
}

void core1_main() {
dvi_register_irqs_this_core(&dvi0, DMA_IRQ_0);
dvi_start(&dvi0);
dvi_scanbuf_main_16bpp(&dvi0);
__builtin_unreachable();
}

void core1_scanline_callback() {
// Discard any scanline pointers passed back
uint16_t *bufptr;
while (queue_try_remove_u32(&dvi0.q_colour_free, &bufptr))
;
bufptr = &framebuf[FRAME_WIDTH * scanline];
queue_add_blocking_u32(&dvi0.q_colour_valid, &bufptr);
scanline = (scanline + 1) % FRAME_HEIGHT;
}

void vsync() {
while(scanline != 0) {};
}

void get_audio_frame(int16_t *buffer, uint32_t sample_count) {
size_t bytes_read = 0;
f_read(&afil, (uint8_t *)buffer, sample_count * 2, &bytes_read);
if(bytes_read == 0) {
f_lseek(&afil, 0);
}
}

int main() {
vreg_set_voltage(VREG_VSEL);
sleep_ms(10);
set_sys_clock_khz(DVI_TIMING.bit_clk_khz, true);

setup_default_uart();

gpio_init(SW_A); gpio_set_dir(SW_A, GPIO_IN); gpio_pull_down(SW_A);
gpio_init(SW_B); gpio_set_dir(SW_B, GPIO_IN); gpio_pull_down(SW_B);
gpio_init(SW_C); gpio_set_dir(SW_C, GPIO_IN); gpio_pull_down(SW_C);

mount_sd();

dvi0.timing = &DVI_TIMING;
dvi0.ser_cfg = DVI_DEFAULT_SERIAL_CONFIG;
dvi0.scanline_callback = core1_scanline_callback;
dvi_init(&dvi0, next_striped_spin_lock_num(), next_striped_spin_lock_num());


sprite_fill16(framebuf, 0xffff, FRAME_WIDTH * FRAME_HEIGHT);
uint16_t *bufptr = framebuf;
queue_add_blocking_u32(&dvi0.q_colour_valid, &bufptr);
bufptr += FRAME_WIDTH;
queue_add_blocking_u32(&dvi0.q_colour_valid, &bufptr);

multicore_launch_core1(core1_main);

size_t bytes_read = 0;

printf("Starting playback...\n");
uint dma_channel = 9;
struct audio_buffer_pool *ap = init_audio(AUDIO_SAMPLE_RATE, PICO_AUDIO_I2S_DATA, PICO_AUDIO_I2S_BCLK, 1, dma_channel);

printf("Opening Video/Audio files...\n");

// Convert video to raw RGB656 litte-endian using ffmpeg, eg:
// ffmpeg -i BigBuckBunny_640x360.m4v -vf scale=400:240,fps=12 -c:v rawvideo -pix_fmt rgb565le BigBuckBunny2.rgb
fr = f_open(&fil, "BigBuckBunny2.rgb", FA_READ);

// Convert audio to raw, signed, 16-bit, little-endian mono using ffmpeg, eg:
// ffmpeg -i BigBuckBunny_640x360.m4v -f s16le -acodec pcm_s16le -ar 22050 -ac 1 BigBuckBunny2.pcm
fr = f_open(&afil, "BigBuckBunny2.pcm", FA_READ);

printf("Playing...\n");

while(true) {
vsync();
update_buffer(ap, get_audio_frame);
f_read(&fil, (uint8_t *)&framebuf, FRAME_WIDTH * FRAME_HEIGHT * 2, &bytes_read);
if(bytes_read == 0) {
f_lseek(&fil, 0);
}
}
}
39 changes: 39 additions & 0 deletions software/apps/raw_av_player/pimoroni_pico_import.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This file can be dropped into a project to help locate the Pimoroni Pico libraries
# It will also set up the required include and module search paths.


function(find_pimoroni_pico)
if (NOT PIMORONI_PICO_PATH)
set(PIMORONI_PICO_PATH "../../../../../pimoroni-pico/")
endif()

if(NOT IS_ABSOLUTE ${PIMORONI_PICO_PATH})
get_filename_component(
PIMORONI_PICO_PATH
"${CMAKE_CURRENT_BINARY_DIR}/${PIMORONI_PICO_PATH}"
ABSOLUTE)
endif()

set(PIMORONI_PICO_PATH ${PIMORONI_PICO_PATH} PARENT_SCOPE)

if (NOT EXISTS ${PIMORONI_PICO_PATH})
message(WARNING "Directory '${PIMORONI_PICO_PATH}' not found")
return()
endif()

if (NOT EXISTS ${PIMORONI_PICO_PATH}/pimoroni_pico_import.cmake)
message(WARNING "Directory '${PIMORONI_PICO_PATH}' does not appear to contain the Pimoroni Pico libraries")
return()
endif()

message("PIMORONI_PICO_PATH is ${PIMORONI_PICO_PATH}")

set(PIMORONI_PICO_PATH ${PIMORONI_PICO_PATH} CACHE PATH "Path to the Pimoroni Pico libraries" FORCE)

include_directories(${PIMORONI_PICO_PATH})
list(APPEND CMAKE_MODULE_PATH ${PIMORONI_PICO_PATH})

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} PARENT_SCOPE)
endfunction()

find_pimoroni_pico()