/* ** AlacUtils.java ** ** Copyright (c) 2011 Peter McQuillan ** ** All Rights Reserved. ** ** Distributed under the BSD Software License (see license.txt) ** */ package com.beatofthedrum.alacdecoder; public class AlacUtils { public static AlacContext AlacOpenFileInput(String inputfilename) { int headerRead; QTMovieT qtmovie = new QTMovieT(); DemuxResT demux_res = new DemuxResT(); AlacContext ac = new AlacContext(); AlacInputStream input_stream; AlacFile alac; ac.error = false; try { java.io.FileInputStream fistream; fistream = new java.io.FileInputStream(inputfilename); input_stream = new AlacInputStream(fistream); } catch (java.io.FileNotFoundException fe) { ac.error_message = "Input file not found"; ac.error = true; return (ac); } ac.input_stream = input_stream; /* if qtmovie_read returns successfully, the stream is up to * the movie data, which can be used directly by the decoder */ headerRead = DemuxUtils.qtmovie_read(input_stream, qtmovie, demux_res); if (headerRead == 0) { ac.error = true; if (demux_res.format_read == 0) { ac.error_message = "Failed to load the QuickTime movie headers."; if (demux_res.format_read != 0) ac.error_message = ac.error_message + " File type: " + DemuxUtils.SplitFourCC(demux_res.format); } else { ac.error_message = "Error while loading the QuickTime movie headers."; } return (ac); } else if(headerRead == 3) { /* ** This section is used when the stream system being used doesn't support seeking ** We have kept track within the file where we need to go to, we close the file and ** skip bytes to go directly to that point */ try { ac.input_stream.close(); } catch(java.io.IOException ioe) { ac.error_message = "Error when seeking to start of music data"; ac.error = true; return (ac); } try { java.io.FileInputStream fistream; fistream = new java.io.FileInputStream(inputfilename); input_stream = new AlacInputStream(fistream); ac.input_stream = input_stream; qtmovie.qtstream.stream = input_stream; qtmovie.qtstream.currentPos = 0; StreamUtils.stream_skip(qtmovie.qtstream, qtmovie.saved_mdat_pos); } catch (java.io.FileNotFoundException fe) { ac.error_message = "Input file not found"; ac.error = true; return (ac); } } /* initialise the sound converter */ alac = AlacDecodeUtils.create_alac(demux_res.sample_size, demux_res.num_channels); AlacDecodeUtils.alac_set_info(alac, demux_res.codecdata); ac.demux_res = demux_res; ac.alac = alac; return (ac); } public static void AlacCloseFile(AlacContext ac) { if(null != ac.input_stream) { try { ac.input_stream.close(); } catch(java.io.IOException ioe) { } } } // Heres where we extract the actual music data public static int AlacUnpackSamples(AlacContext ac, int[] pDestBuffer) { int sample_byte_size; SampleDuration sampleinfo = new SampleDuration(); byte[] read_buffer = ac.read_buffer; int destBufferSize = 1024 *24 * 3; // 24kb buffer = 4096 frames = 1 alac sample (we support max 24bps) int outputBytes; MyStream inputStream = new MyStream(); inputStream.stream = ac.input_stream; // if current_sample_block is beyond last block then finished if(ac.current_sample_block >= ac.demux_res.sample_byte_size.length) { return 0; } if (get_sample_info(ac.demux_res, ac.current_sample_block , sampleinfo) == 0) { // getting sample failed return 0; } sample_byte_size = sampleinfo.sample_byte_size; StreamUtils.stream_read(inputStream, sample_byte_size, read_buffer, 0); /* now fetch */ outputBytes = destBufferSize; outputBytes = AlacDecodeUtils.decode_frame(ac.alac, read_buffer, pDestBuffer, outputBytes); ac.current_sample_block = ac.current_sample_block + 1; outputBytes -= ac.offset * AlacGetBytesPerSample(ac); System.arraycopy(pDestBuffer, ac.offset, pDestBuffer, 0, outputBytes); ac.offset = 0; return outputBytes; } // Returns the sample rate of the specified ALAC file public static int AlacGetSampleRate(AlacContext ac) { if ( null != ac && ac.demux_res.sample_rate != 0) { return ac.demux_res.sample_rate; } else { return (44100); } } public static int AlacGetNumChannels(AlacContext ac) { if ( null != ac && ac.demux_res.num_channels != 0) { return ac.demux_res.num_channels; } else { return 2; } } public static int AlacGetBitsPerSample(AlacContext ac) { if (null != ac && ac.demux_res.sample_size != 0) { return ac.demux_res.sample_size; } else { return 16; } } public static int AlacGetBytesPerSample(AlacContext ac) { if ( null != ac && ac.demux_res.sample_size != 0) { return (int)Math.ceil(ac.demux_res.sample_size/8); } else { return 2; } } // Get total number of samples contained in the Apple Lossless file, or -1 if unknown public static int AlacGetNumSamples(AlacContext ac) { /* calculate output size */ int num_samples = 0; int thissample_duration; int thissample_bytesize = 0; SampleDuration sampleinfo = new SampleDuration(); int i; boolean error_found = false; int retval = 0; for (i = 0; i < ac.demux_res.sample_byte_size.length; i++) { thissample_duration = 0; thissample_bytesize = 0; retval = get_sample_info(ac.demux_res, i, sampleinfo); if(retval == 0) { return (-1); } thissample_duration = sampleinfo.sample_duration; thissample_bytesize = sampleinfo.sample_byte_size; num_samples += thissample_duration; } return (num_samples); } static int get_sample_info(DemuxResT demux_res, int samplenum, SampleDuration sampleinfo) { int duration_index_accum = 0; int duration_cur_index = 0; if (samplenum >= demux_res.sample_byte_size.length) { System.err.println("sample " + samplenum + " does not exist "); return 0; } if (demux_res.num_time_to_samples == 0) // was null { System.err.println("no time to samples"); return 0; } while ((demux_res.time_to_sample[duration_cur_index].sample_count + duration_index_accum) <= samplenum) { duration_index_accum += demux_res.time_to_sample[duration_cur_index].sample_count; duration_cur_index++; if (duration_cur_index >= demux_res.num_time_to_samples) { System.err.println("sample " + samplenum + " does not have a duration"); return 0; } } sampleinfo.sample_duration = demux_res.time_to_sample[duration_cur_index].sample_duration; sampleinfo.sample_byte_size = demux_res.sample_byte_size[samplenum]; return 1; } /** * sets position in pcm samples * @param ac alac context * @param position position in pcm samples to go to */ public static void AlacSetPosition(AlacContext ac, long position) { DemuxResT res = ac.demux_res; int current_position = 0; int current_sample = 0; SampleDuration sample_info = new SampleDuration(); for (int i = 0; i < res.stsc.length; i++) { ChunkInfo chunkInfo = res.stsc[i]; int last_chunk; if (i < res.stsc.length - 1) { last_chunk = res.stsc[i + 1].first_chunk; } else { last_chunk = res.stco.length; } for (int chunk = chunkInfo.first_chunk; chunk <= last_chunk; chunk++) { int pos = res.stco[chunk - 1]; int sample_count = chunkInfo.samples_per_chunk; while (sample_count > 0) { int ret = get_sample_info(res, current_sample, sample_info); if (ret == 0) return; current_position += sample_info.sample_duration; if (position < current_position) { ac.input_stream.seek(pos); ac.current_sample_block = current_sample; ac.offset = (int) (position - (current_position - sample_info.sample_duration)) * AlacGetNumChannels(ac); return; } pos += sample_info.sample_byte_size; current_sample++; sample_count--; } } } } }