diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c61f36a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required (VERSION 2.8.11) +project (spdif-decoder) + +add_executable (spdif-decoder + codechandler.c + helper.c + myspdif.c + myspdifdec.c + resample.c + spdif-loop.c +) + +SET(FFMPEG ${CMAKE_CURRENT_SOURCE_DIR}/../ffmpeg-4.3.1) + +target_include_directories (spdif-decoder + PUBLIC ${FFMPEG} +) + +FIND_LIBRARY(libavcodec avcodec ${FFMPEG}/libavcodec) +FIND_LIBRARY(libavformat avformat ${FFMPEG}/libavformat) +FIND_LIBRARY(libavdevice avdevice ${FFMPEG}/libavdevice) +FIND_LIBRARY(libavutil avutil ${FFMPEG}/libavutil) +FIND_LIBRARY(libswresample swresample ${FFMPEG}/libswresample) +FIND_LIBRARY(libavfilter avfilter ${FFMPEG}/libavfilter) +TARGET_LINK_LIBRARIES(spdif-decoder + ${libavcodec} + ${libavformat} + ${libavdevice} + ${libavutil} + ${libswresample} + ${libavfilter} + ao + m +) diff --git a/Makefile b/Makefile deleted file mode 100644 index af8e2ef..0000000 --- a/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -PROG= spdif-loop - -CFLAGS+= -Wall -std=c99 -g -LDFLAGS+= -lavcodec -lavformat -lavdevice -lavutil -lao -lm - -all: ${PROG} - -clean: - -rm -f ${PROG} diff --git a/NOTES b/NOTES deleted file mode 100644 index 3b919d0..0000000 --- a/NOTES +++ /dev/null @@ -1,50 +0,0 @@ -libavformat sequence - -init - -av_register_all(); -avcodec_register_all(); - -sndinfmt = av_find_input_format("alsa"); -avformat_open_input(&sndinctx, "hw:1", sndinfmt, NULL); - -mock up another input for spdif demux: - -spdifctx = avformat_alloc_context(); -spdifctx->pb = avio_alloc_context(buf, bufsize, 0, mydata, read_cb, NULL, NULL); -spdiffmt = av_find_input_format("spdif"); -avformat_open_input(&spdifctx, NULL, spidffmt, NULL); - -get decoder - -codec = avcodec_find_decoder(spdifcodec_id); -codecctx = avcodec_alloc_context3(codec); -avcodec_open2(&codecctx, codec, NULL); - -outframe = avcodec_alloc_frame(); -processed_len = avcodec_decode_audio4(codecctx, outframe, &got_frame, inpkt); - -/* advance inpkt by processed_len */ - -codecctx->sample_rate; -codecctx->channels; -codecctx->sample_fmt; -codecctx->channel_layout; - -outframe->data -outframe->nb_samples -outlen = av_samples_get_buffer_size(NULL, codecctx->channels, outframe->nb_samples, codecctx->sample_fmt, 1); - -play with libao - -ao_initialize(); -ao_sample_format outfmt; -memset(&outfmt, 0, sizeof(outfmt)); -outfmt.bits = ...; -outfmt.channels = ...; -outfmt.rate = ...; -outfmt.byte_format = ...; - -outdev = ao_open_live(ao_default_driver_id(), &outfmt, NULL); - -ao_play(outdev, buf, bufsize); diff --git a/README.md b/README.md index 80c32fd..491c732 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,39 @@ -spdif-loop -========== +spdif-decoder +============= SPDIF to 5.1 software loop decoder. Use if you want to connect a -digital surround signal (Dolby Digital), such as an Xbox 360 S, via +digital surround signal (Dolby Digital), such as an Xbox 360 S or TV, via your PC to your 5.1 (analog) stereo. Like a digital receiver/decoder, just in software. +FFMPEG is licenced under the GNU Lesser General Public License version 2.1 and so is +this piece of software. Requirements ------------ +- libasound2-dev +- libao-dev +- cmake +- ffmpeg from Source https://www.ffmpeg.org/download.html (tested with 4.3.1) -- ffmpeg/libav -- libao - - -Build +Build ffmpeg shared library with a minimal set for alsa and AC3 support ----- + ./configure --enable-shared --disable-static --disable-everything --enable-demuxer=spdif --enable-decoder=ac3 --enable-indev=alsa + make -make +Build spdif-decoder +----- +Prepare CMakeLists.txt - set FFMPEG Var with Path to ffmpeg (if not in ../ffmpeg-4.3.1) + cmake . + make Run --- I run it like this: - ./spdif-loop -i hw:CARD=Device -d pulse -o alsa_output.usb-0d8c_USB_Sound_Device-00-Device.analog-surround51 + ./spdif-decoder -i hw:CARD=Device -d pulse -o alsa_output.usb-0d8c_USB_Sound_Device-00-Device.analog-surround51 Alsa's `hw:CARD=Device` is my SPDIF input. You can list your alsa devices with @@ -39,7 +47,7 @@ I had to use `amixer` to set the capture source to SPIF. In this case amixer -c Device set 'PCM Capture Source' 'IEC958 In' # set input -Contact +Thanks to ------- - +Sebastian Morgenstern Simon Schubert <2@0x2c.org> diff --git a/codechandler.c b/codechandler.c new file mode 100644 index 0000000..e4570ab --- /dev/null +++ b/codechandler.c @@ -0,0 +1,102 @@ +/* + * codechandler.c + * + * Created on: 25.04.2015 + * Author: sebastian + */ + +#include +#include +#include +#include "resample.h" +#include "codechandler.h" + +void CodecHandler_init(CodecHandler* h){ + h->codec = NULL; + h->codecContext = NULL; + h->currentChannelCount = 0; + h->currentCodecID = AV_CODEC_ID_NONE; + h->currentSampleRate = 0; + h->swr = resample_init(); + h->frame = av_frame_alloc(); +} +void CodecHandler_deinit(CodecHandler* h){ + resample_deinit(h->swr); + av_frame_free(&h->frame); +} + +int CodecHandler_loadCodec(CodecHandler * handler, AVFormatContext * formatcontext){ + if (formatcontext->nb_streams == 0){ + printf("could not find a stream\n"); + handler->currentCodecID = AV_CODEC_ID_NONE; + return -1; + } + + if(handler->currentCodecID == formatcontext->streams[0]->codec->codec_id){ + //Codec already loaded + return 0; + } + + if(handler->codecContext != NULL){ + CodecHandler_closeCodec(handler); + } + handler->currentCodecID = AV_CODEC_ID_NONE; + + handler->codec = avcodec_find_decoder(formatcontext->streams[0]->codec->codec_id); + if (!handler->codec) { + printf("could not find codec\n"); + return -1; + }else{ + printf("found codec\n"); + } + handler->codecContext = avcodec_alloc_context3(handler->codec); + if (!handler->codecContext) + errx(1, "cannot allocate codec"); + if (avcodec_open2(handler->codecContext, handler->codec, NULL) != 0) + errx(1, "cannot open codec"); + handler->currentCodecID = formatcontext->streams[0]->codec->codec_id; + return 0; +} + +int CodecHandler_decodeCodec(CodecHandler * h, AVPacket * pkt, + uint8_t *outbuffer, uint32_t* bufferfilled){ + int got_frame; + int processed_len = avcodec_decode_audio4(h->codecContext, h->frame, &got_frame, pkt); + if (processed_len < 0) + errx(1, "cannot decode input"); + + int ret = 0; + + pkt->data += processed_len; + pkt->size -= processed_len; + if(h->currentChannelCount != h->codecContext->channels + || h->currentSampleRate != h->codecContext->sample_rate + || h->currentChannelLayout != h->codecContext->channel_layout){ + resample_loadFromCodec(h->swr, h->codecContext); + printf("c: %d, s: %d\n",h->codecContext->channels, h->codecContext->sample_rate); + ret = 1; + } + + swr_convert(h->swr, &outbuffer, h->frame->nb_samples, (const uint8_t **)h->frame->data, h->frame->nb_samples); + *bufferfilled = av_samples_get_buffer_size(NULL, + h->codecContext->channels, + h->frame->nb_samples, + AV_SAMPLE_FMT_S16, + 1); + + h->currentChannelCount = h->codecContext->channels; + h->currentSampleRate = h->codecContext->sample_rate; + h->currentChannelLayout = h->codecContext->channel_layout; + return ret; +} + + +int CodecHandler_closeCodec(CodecHandler * handler){ + if(handler->codecContext != NULL){ + avcodec_close(handler->codecContext); + avcodec_free_context(&handler->codecContext); + } + handler->codec = NULL; + handler->codecContext = NULL; + return 0; +} diff --git a/codechandler.h b/codechandler.h new file mode 100644 index 0000000..6a858c2 --- /dev/null +++ b/codechandler.h @@ -0,0 +1,37 @@ +/* + * codechandler.h + * + * Created on: 25.04.2015 + * Author: sebastian + */ + +#ifndef CODECHANDLER_H_ +#define CODECHANDLER_H_ +#include +#include +#include +#include + +typedef struct s_codechandler{ + AVCodecContext *codecContext; + AVCodec * codec; + enum AVCodecID currentCodecID; + int currentChannelCount; + uint64_t currentChannelLayout; + int currentSampleRate; + SwrContext * swr; + AVFrame * frame; +} CodecHandler; + +void CodecHandler_init(CodecHandler* handler); +void CodecHandler_deinit(CodecHandler* handler); + +int CodecHandler_loadCodec(CodecHandler * handler, AVFormatContext * formatcontext); +int CodecHandler_hasCodecChangend(CodecHandler * handler, AVFormatContext * formatcontext); + +int CodecHandler_decodeCodec(CodecHandler * h, AVPacket * pkt, + uint8_t *outbuffer, uint32_t* bufferfilled); +int CodecHandler_closeCodec(CodecHandler * handler); + + +#endif /* CODECHANDLER_H_ */ diff --git a/helper.c b/helper.c new file mode 100644 index 0000000..7928a4a --- /dev/null +++ b/helper.c @@ -0,0 +1,80 @@ +/* + * helper.c + * + * Created on: 21.04.2015 + * Author: sebastian + */ + +#include +#include +#include +#include +#include + +#include + +ao_device * open_output(int driver_id, ao_option *dev_opts, int bits, int channels, int sample_rate) +{ + printf("%d bit, %d channels, %dHz\n", + bits, + channels, + sample_rate); + + ao_sample_format out_fmt = { + .bits = bits, + .channels = channels, + .rate = sample_rate, + .byte_format = AO_FMT_NATIVE, + //.matrix = "L,R,BL,BR,C,LFE", + .matrix = "L,R,C,LFE,BL,BR", + }; + + return (ao_open_live(driver_id, &out_fmt, dev_opts)); +} + +int test_audio_out(int driver_id, ao_option *dev_opts) +{ + struct chan_map { + const char *name; + int freq; + int idx; + } map[] = { + /* This needs to match the order in open_output(). */ + { "left", 500, 0 }, + { "center", 500, 2 }, + { "right", 500, 1 }, + { "rear right", 500, 5 }, + { "rear left", 500, 4 }, + { "sub", 50, 3 } + }; + + ao_device *odev = open_output(driver_id, dev_opts, 16, 6, 48000); + if (!odev) + errx(1, "cannot open audio output"); + int ch; + for (ch = 0; ch < 6; ++ch) { + const size_t buflen = 4800; /* 1/10 of a second */ + int16_t buf[buflen * 6]; + + printf("channel %d: %s\n", map[ch].idx, map[ch].name); + + /* prepare sine samples */ + memset(buf, 0, sizeof(buf)); + int i; + for (i = 0; i < buflen; ++i) { + buf[i * 6 + map[ch].idx] = INT16_MAX / 10 * cos(2 * (3.14159265358979323846) * map[ch].freq * i / 48000.0); + } + + /* play for 2 sec, 1 sec pause */ + for (i = 0; i < 30; ++i) { + if (i == 20) { + /* now pause */ + memset(buf, 0, sizeof(buf)); + } + if (!ao_play(odev, (char *)buf, sizeof(buf))) + errx(1, "cannot play test audio"); + } + } + + return (0); +} diff --git a/helper.h b/helper.h new file mode 100644 index 0000000..3324542 --- /dev/null +++ b/helper.h @@ -0,0 +1,16 @@ +/* + * helper.h + * + * Created on: 21.04.2015 + * Author: sebastian + */ + +#ifndef HELPER_H_ +#define HELPER_H_ + +#include +#include + +extern ao_device * open_output(int driver_id, ao_option *dev_opts, int bits, int channels, int sample_rate); +extern int test_audio_out(int driver_id, ao_option *dev_opts); +#endif /* HELPER_H_ */ diff --git a/myspdif.c b/myspdif.c new file mode 100644 index 0000000..c4548c1 --- /dev/null +++ b/myspdif.c @@ -0,0 +1,42 @@ +/* + * IEC 61937 common code + * Copyright (c) 2009 Bartlomiej Wolowiec + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "myspdif.h" +#include + +//TODO move to DSP +void my_spdif_bswap_buf16(uint16_t *dst, const uint16_t *src, int w) +{ + int i; + + for (i = 0; i + 8 <= w; i += 8) { + dst[i + 0] = av_bswap16(src[i + 0]); + dst[i + 1] = av_bswap16(src[i + 1]); + dst[i + 2] = av_bswap16(src[i + 2]); + dst[i + 3] = av_bswap16(src[i + 3]); + dst[i + 4] = av_bswap16(src[i + 4]); + dst[i + 5] = av_bswap16(src[i + 5]); + dst[i + 6] = av_bswap16(src[i + 6]); + dst[i + 7] = av_bswap16(src[i + 7]); + } + for (; i < w; i++) + dst[i + 0] = av_bswap16(src[i + 0]); +} diff --git a/myspdif.h b/myspdif.h new file mode 100644 index 0000000..2b5582a --- /dev/null +++ b/myspdif.h @@ -0,0 +1,71 @@ +/* + * IEC 61937 common header + * Copyright (c) 2009 Bartlomiej Wolowiec + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MYSPDIF_H_ +#define MYSPDIF_H_ + +#include +#include + +#define SYNCWORD1 0xF872 +#define SYNCWORD2 0x4E1F +#define BURST_HEADER_SIZE 0x8 +#define SPDIF_MAX_OFFSET 16384 + +/* +enum myIEC61937DataType { + IEC61937_AC3 = 0x01, ///< AC-3 data + IEC61937_MPEG1_LAYER1 = 0x04, ///< MPEG-1 layer 1 + IEC61937_MPEG1_LAYER23 = 0x05, ///< MPEG-1 layer 2 or 3 data or MPEG-2 without extension + IEC61937_MPEG2_EXT = 0x06, ///< MPEG-2 data with extension + IEC61937_MPEG2_AAC = 0x07, ///< MPEG-2 AAC ADTS + IEC61937_MPEG2_LAYER1_LSF = 0x08, ///< MPEG-2, layer-1 low sampling frequency + IEC61937_MPEG2_LAYER2_LSF = 0x09, ///< MPEG-2, layer-2 low sampling frequency + IEC61937_MPEG2_LAYER3_LSF = 0x0A, ///< MPEG-2, layer-3 low sampling frequency + IEC61937_DTS1 = 0x0B, ///< DTS type I (512 samples) + IEC61937_DTS2 = 0x0C, ///< DTS type II (1024 samples) + IEC61937_DTS3 = 0x0D, ///< DTS type III (2048 samples) + IEC61937_ATRAC = 0x0E, ///< ATRAC data + IEC61937_ATRAC3 = 0x0F, ///< ATRAC3 data + IEC61937_ATRACX = 0x10, ///< ATRAC3+ data + IEC61937_DTSHD = 0x11, ///< DTS HD data + IEC61937_WMAPRO = 0x12, ///< WMA 9 Professional data + IEC61937_MPEG2_AAC_LSF_2048 = 0x13, ///< MPEG-2 AAC ADTS half-rate low sampling frequency + IEC61937_MPEG2_AAC_LSF_4096 = 0x13 | 0x20, ///< MPEG-2 AAC ADTS quarter-rate low sampling frequency + IEC61937_EAC3 = 0x15, ///< E-AC-3 data + IEC61937_TRUEHD = 0x16, ///< TrueHD data +}; +*/ +/* +static const uint16_t spdif_mpeg_pkt_offset[2][3] = { + //LAYER1 LAYER2 LAYER3 + { 3072, 9216, 4608 }, // MPEG2 LSF + { 1536, 4608, 4608 }, // MPEG1 +}; +*/ + +void my_spdif_bswap_buf16(uint16_t *dst, const uint16_t *src, int w); +int my_spdif_read_packet(AVFormatContext *s, AVPacket *pkt, + uint8_t * garbagebuffer, int garbagebuffersize, int * garbagebufferfilled); +int my_spdif_probe(const uint8_t *p_buf, int buf_size, enum AVCodecID *codec); + + +#endif /* MYSPDIF_H_ */ diff --git a/myspdifdec.c b/myspdifdec.c new file mode 100644 index 0000000..be30759 --- /dev/null +++ b/myspdifdec.c @@ -0,0 +1,174 @@ +/* + * IEC 61937 demuxer + * Copyright (c) 2010 Anssi Hannula + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * IEC 61937 demuxer, used for compressed data in S/PDIF + * @author Anssi Hannula + */ + +#include +#include +#include "myspdif.h" +#include +#include "libavcodec/adts_parser.h" +#include "libavutil/bswap.h" + +static int spdif_get_offset_and_codec(AVFormatContext *s, + enum IEC61937DataType data_type, + const char *buf, int *offset, + enum AVCodecID *codec) +{ + uint32_t samples; + uint8_t frames; + int ret; + + switch (data_type & 0xff) { + case IEC61937_AC3: + *offset = AC3_FRAME_SIZE << 2; + *codec = AV_CODEC_ID_AC3; + break; + case IEC61937_MPEG1_LAYER1: + *offset = spdif_mpeg_pkt_offset[1][0]; + *codec = AV_CODEC_ID_MP1; + break; + case IEC61937_MPEG1_LAYER23: + *offset = spdif_mpeg_pkt_offset[1][0]; + *codec = AV_CODEC_ID_MP3; + break; + case IEC61937_MPEG2_EXT: + *offset = 4608; + *codec = AV_CODEC_ID_MP3; + break; + case IEC61937_MPEG2_AAC: + ret = av_adts_header_parse(buf, &samples, &frames); + if (ret < 0) { + if (s) /* be silent during a probe */ + av_log(s, AV_LOG_ERROR, "Invalid AAC packet in IEC 61937\n"); + return ret; + } + *offset = samples << 2; + *codec = AV_CODEC_ID_AAC; + break; + case IEC61937_MPEG2_LAYER1_LSF: + *offset = spdif_mpeg_pkt_offset[0][0]; + *codec = AV_CODEC_ID_MP1; + break; + case IEC61937_MPEG2_LAYER2_LSF: + *offset = spdif_mpeg_pkt_offset[0][1]; + *codec = AV_CODEC_ID_MP2; + break; + case IEC61937_MPEG2_LAYER3_LSF: + *offset = spdif_mpeg_pkt_offset[0][2]; + *codec = AV_CODEC_ID_MP3; + break; + case IEC61937_DTS1: + *offset = 2048; + *codec = AV_CODEC_ID_DTS; + break; + case IEC61937_DTS2: + *offset = 4096; + *codec = AV_CODEC_ID_DTS; + break; + case IEC61937_DTS3: + *offset = 8192; + *codec = AV_CODEC_ID_DTS; + break; + default: + return AVERROR_PATCHWELCOME; + } + return 0; +} + +int my_spdif_read_packet(AVFormatContext *s, AVPacket *pkt, + uint8_t * garbagebuffer, int garbagebuffersize, int * garbagebufferfilled) +{ + AVIOContext *pb = s->pb; + enum IEC61937DataType data_type; + enum AVCodecID codec_id; + uint32_t state = 0; + int pkt_size_bits, offset, ret; + *garbagebufferfilled = 0; + while (state != (AV_BSWAP16C(SYNCWORD1) << 16 | AV_BSWAP16C(SYNCWORD2))) { + if(*garbagebufferfilled < garbagebuffersize){ + *garbagebuffer = avio_r8(pb); + (*garbagebufferfilled)++; + + state = (state << 8) | *garbagebuffer; + garbagebuffer++; + + if (avio_feof(pb)){ + return AVERROR_EOF; + } + }else{ + return AVERROR_STREAM_NOT_FOUND; + } + } + *garbagebufferfilled -= 4; + data_type = avio_rl16(pb); + pkt_size_bits = avio_rl16(pb); + + if (pkt_size_bits % 16) + avpriv_request_sample(s, "Packet not ending at a 16-bit boundary"); + + ret = av_new_packet(pkt, FFALIGN(pkt_size_bits, 16) >> 3); + if (ret) + return ret; + + pkt->pos = avio_tell(pb) - BURST_HEADER_SIZE; + + if (avio_read(pb, pkt->data, pkt->size) < pkt->size) { + av_free_packet(pkt); + return AVERROR_EOF; + } + my_spdif_bswap_buf16((uint16_t *)pkt->data, (uint16_t *)pkt->data, pkt->size >> 1); + + ret = spdif_get_offset_and_codec(s, data_type, pkt->data, + &offset, &codec_id); + if (ret) { + av_free_packet(pkt); + return ret; + } + + /* skip over the padding to the beginning of the next frame */ + avio_skip(pb, offset - pkt->size - BURST_HEADER_SIZE); + + if (!s->nb_streams) { + /* first packet, create a stream */ + AVStream *st = avformat_new_stream(s, NULL); + if (!st) { + av_free_packet(pkt); + return AVERROR(ENOMEM); + } + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->codec_id = codec_id; + } else if (codec_id != s->streams[0]->codec->codec_id) { + avpriv_report_missing_feature(s, "Codec change in IEC 61937"); + return AVERROR_PATCHWELCOME; + } + + if (!s->bit_rate && s->streams[0]->codec->sample_rate) + /* stream bitrate matches 16-bit stereo PCM bitrate for currently + supported codecs */ + s->bit_rate = 2 * 16 * s->streams[0]->codec->sample_rate; + + return 0; +} diff --git a/resample.c b/resample.c new file mode 100644 index 0000000..bd2c773 --- /dev/null +++ b/resample.c @@ -0,0 +1,36 @@ +/* + * resample.c + * + * Created on: 20.04.2015 + * Author: sebastian + */ +#include "resample.h" + +#include + +SwrContext* resample_init(){ + return swr_alloc(); +} + +void resample_deinit(SwrContext* swr){ + swr_free(&swr); +} + +void resample_loadFromCodec(SwrContext *swr, AVCodecContext* audioCodec){ + // Set up SWR context once you've got codec information + av_opt_set_int(swr, "in_channel_layout", audioCodec->channel_layout, 0); + av_opt_set_int(swr, "out_channel_layout", audioCodec->channel_layout, 0); + av_opt_set_int(swr, "in_sample_rate", audioCodec->sample_rate, 0); + av_opt_set_int(swr, "out_sample_rate", audioCodec->sample_rate, 0); + av_opt_set_sample_fmt(swr, "in_sample_fmt", audioCodec->sample_fmt, 0); + av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + swr_init(swr); +} + +void resample_do(SwrContext* swr, AVFrame *audioFrame, uint8_t* outputBuffer){ + swr_convert(swr, + &outputBuffer, + audioFrame->nb_samples, + (const uint8_t**)audioFrame->data, + audioFrame->nb_samples); +} diff --git a/resample.h b/resample.h new file mode 100644 index 0000000..504cd86 --- /dev/null +++ b/resample.h @@ -0,0 +1,21 @@ +/* + * resample.h + * + * Created on: 20.04.2015 + * Author: sebastian + */ + +#ifndef RESAMPLE_H_ +#define RESAMPLE_H_ + +#include +#include +#include +#include + +SwrContext* resample_init(); +void resample_deinit(SwrContext* swr); +void resample_loadFromCodec(SwrContext *swr, AVCodecContext* audioCodec); +void resample_do(SwrContext* swr, AVFrame *audioFrame, uint8_t* outputBuffer); + +#endif /* RESAMPLE_H_ */ diff --git a/spdif-loop.c b/spdif-loop.c index 4c4012b..4faf934 100644 --- a/spdif-loop.c +++ b/spdif-loop.c @@ -10,8 +10,18 @@ #include #include #include +#include +#include + +#include "resample.h" +#include "helper.h" +#include "myspdif.h" +#include "codechandler.h" + +//#define DEBUG +//#define IO_BUFFER_SIZE SPDIF_MAX_OFFSET// ((8+1792+4344)*1) +#define IO_BUFFER_SIZE ((8+1792+4344)*1) -#define IO_BUFFER_SIZE 32768 struct alsa_read_state { AVFormatContext *ctx; @@ -56,7 +66,8 @@ alsa_reader(void *data, uint8_t *buf, int buf_size) if (debug_data) { static int had_zeros = 0; - for (int i = 0; i < read_size; ++i) { + int i; + for (i = 0; i < read_size; ++i) { const char zeros[16] = {0}; if (i % 16 == 0 && read_size - i >= 16 && memcmp((char *)buf + i, zeros, 16) == 0) { @@ -83,88 +94,6 @@ alsa_reader(void *data, uint8_t *buf, int buf_size) return (read_size); } -static enum CodecID -probe_codec(AVFormatContext *s) -{ - AVPacket pkt; - - av_init_packet(&pkt); - if (av_read_frame(s, &pkt) != 0) - return (CODEC_ID_NONE); - - av_free_packet(&pkt); - - if (s->nb_streams == 0) - return (CODEC_ID_NONE); - return (s->streams[0]->codec->codec_id); -} - -static ao_device * -open_output(int driver_id, ao_option *dev_opts, int bits, int channels, int sample_rate) -{ - printf("%d bit, %d channels, %dHz\n", - bits, - channels, - sample_rate); - - ao_sample_format out_fmt = { - .bits = bits, - .channels = channels, - .rate = sample_rate, - .byte_format = AO_FMT_NATIVE, - .matrix = "L,R,C,LFE,BL,BR", - }; - - return (ao_open_live(driver_id, &out_fmt, dev_opts)); -} - -static int -test_audio_out(int driver_id, ao_option *dev_opts) -{ - struct chan_map { - const char *name; - int freq; - int idx; - } map[] = { - /* This needs to match the order in open_output(). */ - { "left", 500, 0 }, - { "center", 500, 2 }, - { "right", 500, 1 }, - { "rear right", 500, 5 }, - { "rear left", 500, 4 }, - { "sub", 50, 3 } - }; - - ao_device *odev = open_output(driver_id, dev_opts, 16, 6, 48000); - if (!odev) - errx(1, "cannot open audio output"); - - for (int ch = 0; ch < 6; ++ch) { - const size_t buflen = 4800; /* 1/10 of a second */ - int16_t buf[buflen * 6]; - - printf("channel %d: %s\n", map[ch].idx, map[ch].name); - - /* prepare sine samples */ - memset(buf, 0, sizeof(buf)); - for (int i = 0; i < buflen; ++i) { - buf[i * 6 + map[ch].idx] = INT16_MAX / 10 * cos(2 * M_PI * map[ch].freq * i / 48000.0); - } - - /* play for 2 sec, 1 sec pause */ - for (int i = 0; i < 30; ++i) { - if (i == 20) { - /* now pause */ - memset(buf, 0, sizeof(buf)); - } - if (!ao_play(odev, (char *)buf, sizeof(buf))) - errx(1, "cannot play test audio"); - } - } - - return (0); -} - int main(int argc, char **argv) { @@ -172,8 +101,8 @@ main(int argc, char **argv) char *alsa_dev_name = NULL; char *out_driver_name = NULL; char *out_dev_name = NULL; - - for (int opt = 0; (opt = getopt(argc, argv, "d:hi:o:tv")) != -1;) { + int opt; + for (opt = 0; (opt = getopt(argc, argv, "d:hi:o:tv")) != -1;) { switch (opt) { case 'd': out_driver_name = optarg; @@ -211,12 +140,14 @@ main(int argc, char **argv) avdevice_register_all(); ao_initialize(); + ao_option *out_dev_opts = NULL; if (out_dev_name) { if (!ao_append_option(&out_dev_opts, "dev", out_dev_name)) errx(1, "cannot set output device `%s'", out_dev_name); } + int out_driver_id = ao_default_driver_id(); if (out_driver_name) out_driver_id = ao_driver_id(out_driver_name); @@ -226,16 +157,17 @@ main(int argc, char **argv) if (opt_test) { exit(test_audio_out(out_driver_id, out_dev_opts)); - /* NOTREACHED */ + // NOTREACHED } + AVInputFormat *alsa_fmt = av_find_input_format("alsa"); if (!alsa_fmt) errx(1, "cannot find alsa input driver"); AVInputFormat *spdif_fmt = av_find_input_format("spdif"); if (!spdif_fmt) - errx(1, "cannot find spdif demux driver"); + errx(1, "cannot find S/PDIF demux driver"); const int alsa_buf_size = IO_BUFFER_SIZE; unsigned char *alsa_buf = av_malloc(alsa_buf_size); @@ -246,9 +178,10 @@ main(int argc, char **argv) AVFormatContext *alsa_ctx = NULL; ao_device *out_dev = NULL; + char *resamples = malloc(1*1024*1024); + if (0) { retry: - printf("failure...\n"); if (spdif_ctx) avformat_close_input(&spdif_ctx); if (alsa_ctx) @@ -261,10 +194,9 @@ main(int argc, char **argv) printf("retrying.\n"); } - spdif_ctx = avformat_alloc_context(); if (!spdif_ctx) - errx(1, "cannot allocate spdif context"); + errx(1, "cannot allocate S/PDIF context"); if (avformat_open_input(&alsa_ctx, alsa_dev_name, alsa_fmt, NULL) != 0) errx(1, "cannot open alsa input"); @@ -272,105 +204,85 @@ main(int argc, char **argv) struct alsa_read_state read_state = { .ctx = alsa_ctx, }; + av_init_packet(&read_state.pkt); + AVIOContext * avio_ctx = avio_alloc_context(alsa_buf, alsa_buf_size, 0, &read_state, alsa_reader, NULL, NULL); + if (!avio_ctx) { + errx(1, "cannot open avio_alloc_context"); + } spdif_ctx->pb = avio_alloc_context(alsa_buf, alsa_buf_size, 0, &read_state, alsa_reader, NULL, NULL); if (!spdif_ctx->pb) errx(1, "cannot set up alsa reader"); if (avformat_open_input(&spdif_ctx, "internal", spdif_fmt, NULL) != 0) - errx(1, "cannot open spdif input"); + errx(1, "cannot open S/PDIF input"); - enum CodecID spdif_codec_id = probe_codec(spdif_ctx); + av_dump_format(alsa_ctx, 0, alsa_dev_name, 0); -#if HAVE_AVCODEC_GET_NAME - printf("detected spdif codec %s\n", avcodec_get_name(spdif_codec_id)); -#endif + AVPacket pkt = {.size = 0, .data = NULL}; + av_init_packet(&pkt); - AVCodec *spdif_codec = avcodec_find_decoder(spdif_codec_id); - if (!spdif_codec) { - printf("could not find codec\n"); - goto retry; - } + uint32_t howmuch = 0; - AVCodecContext *spdif_codec_ctx = avcodec_alloc_context3(spdif_codec); - if (!spdif_codec_ctx) - errx(1, "cannot allocate codec"); - spdif_codec_ctx->request_sample_fmt = AV_SAMPLE_FMT_S16; - if (avcodec_open2(spdif_codec_ctx, spdif_codec, NULL) != 0) - errx(1, "cannot open codec"); - - AVPacket pkt, pkt1 = {.size = 0, .data = NULL}; - av_init_packet(&pkt1); - pkt = pkt1; - - AVFrame frame; - - for (;;) { - if (pkt.size == 0) { - av_free_packet(&pkt1); - int e = av_read_frame(spdif_ctx, &pkt1); - if (e != 0) { - printf("reading frame failed: %d\n", e); + CodecHandler codecHanlder; + CodecHandler_init(&codecHanlder); + printf("start loop\n"); + while (1) { + int r = my_spdif_read_packet(spdif_ctx, &pkt, (uint8_t*)resamples, IO_BUFFER_SIZE, &howmuch); + + if(r == 0){ + if(CodecHandler_loadCodec(&codecHanlder, spdif_ctx)!=0){ + printf("Could not load codec %s.\n", avcodec_get_name(codecHanlder.currentCodecID)); goto retry; } - pkt = pkt1; - } - avcodec_get_frame_defaults(&frame); - int got_frame = 0; - int processed_len = avcodec_decode_audio4(spdif_codec_ctx, &frame, &got_frame, &pkt); - if (processed_len < 0) - errx(1, "cannot decode input"); - pkt.data += processed_len; - pkt.size -= processed_len; + if(CodecHandler_decodeCodec(&codecHanlder,&pkt,(uint8_t*)resamples, &howmuch) == 1){ + //channel count has changed + //close out_dev + if (out_dev) { + ao_close(out_dev); + out_dev = NULL; + } + printf("Detected S/PDIF codec %s\n", avcodec_get_name(codecHanlder.currentCodecID)); + } + if(pkt.size != 0){ + printf("still some bytes left %d\n",pkt.size); + } + } else { + if(codecHanlder.currentCodecID != AV_CODEC_ID_NONE || + codecHanlder.currentChannelCount != 2 || + codecHanlder.currentSampleRate != 48000){ - if (!got_frame) - continue; + printf("Detected S/PDIF uncompressed audio\n"); + if (out_dev) { + ao_close(out_dev); + out_dev = NULL; + } + } + codecHanlder.currentCodecID = AV_CODEC_ID_NONE; + codecHanlder.currentChannelCount = 2; + codecHanlder.currentSampleRate = 48000; + codecHanlder.currentChannelLayout = 0; + } if (!out_dev) { - /** - * We open the output only here, because we need a full frame decoded - * before we can know the output format. - */ out_dev = open_output(out_driver_id, out_dev_opts, - av_get_bytes_per_sample(spdif_codec_ctx->sample_fmt) * 8, - spdif_codec_ctx->channels, - spdif_codec_ctx->sample_rate); + av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * 8, + codecHanlder.currentChannelCount, + codecHanlder.currentSampleRate); if (!out_dev) errx(1, "cannot open audio output"); } - - int framesize = av_samples_get_buffer_size(NULL, - spdif_codec_ctx->channels, - frame.nb_samples, - spdif_codec_ctx->sample_fmt, - 1); - -#if DEBUG - int max = 0; - int16_t *fb = (void *)frame.data[0]; - for (int i = 0; i < frame.nb_samples * spdif_codec_ctx->channels; ++i) { - int v = fb[i]; - if (v < 0) - v = -v; - if (v > max) - max = v; - } - - /* Debug latency */ - for (int i = 0; i < max / 100; ++i) - putchar('*'); - printf("\n"); - //printf("%d\n", max); -#endif - - if (!ao_play(out_dev, (void *)frame.data[0], framesize)) { + //found wav + if(!ao_play(out_dev, resamples, howmuch)){ + printf("Could not play audio to output device..."); goto retry; } + av_packet_unref(&pkt); } - + CodecHandler_deinit(&codecHanlder); return (0); }