All Downloads are FREE. Search and download functionalities are using the official Maven repository.

playn.robovm.CAFLoader Maven / Gradle / Ivy

/**
 * Copyright 2014 The PlayN Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package playn.robovm;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.robovm.apple.foundation.NSData;
import org.robovm.apple.foundation.NSErrorException;

import static playn.robovm.OpenAL.*;

/**
 * Loads CAFF audio data and uploads it to an OpenAL buffer.
 *
 * 

Adapted from {@code https://github.com/zite/OpenALHelper}.

*/ public class CAFLoader { /** Contains data from the Audio Description chunk of a CAFF file. */ public static class CAFDesc { /** The number of sample frames per second of the data. You can combine this value with the * frames per packet to determine the amount of time represented by a packet. This value must * be nonzero. */ public double sampleRate; /** A four-character code indicating the general kind of data in the stream. This value must be * nonzero. */ public String formatID; /** Flags specific to each format. May be set to 0 to indicate no format flags. */ public int formatFlags; /** The number of bytes in a packet of data. For formats with a variable packet size, this * field is set to 0. In that case, the file must include a Packet Table chunk “Packet Table * Chunk.” Packets are always aligned to a byte boundary. */ public int bytesPerPacket; /** The number of sample frames in each packet of data. For compressed formats, this field * indicates the number of frames encoded in each packet. For formats with a variable number of * frames per packet, this field is set to 0 and the file must include a Packet Table chunk * “Packet Table Chunk.” */ public int framesPerPacket; /** The number of channels in each frame of data. This value must be nonzero. */ public int channelsPerFrame; /** The number of bits of sample data for each channel in a frame of data. This field must be * set to 0 if the data format (for instance any compressed format) does not contain separate * samples for each channel. */ public int bitsPerChannel; public CAFDesc(byte[] data) { this(ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN)); } public CAFDesc(ByteBuffer buf) { sampleRate = buf.getDouble(); formatID = getString(buf, 4); formatFlags = buf.getInt(); bytesPerPacket = buf.getInt(); framesPerPacket = buf.getInt(); channelsPerFrame = buf.getInt(); } public int getALFormat() { switch (channelsPerFrame) { case 1: return (bitsPerChannel == 8) ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16; case 2: return (bitsPerChannel == 8) ? AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16; default: return AL_FORMAT_STEREO16; } } @Override public String toString() { return String.format( "CAFHeader: sampleRate=%f formatID=%s formatFlags=%x bytesPerPacket=%d " + "framesPerPacket=%d channelsPerFrame=%d bitsPerChannel=%d", sampleRate, formatID, formatFlags, bytesPerPacket, framesPerPacket, channelsPerFrame, bitsPerChannel); } } public static void load(File path, int bufferId) { NSData data = null; try { // mmap (if possible) the audio file for efficient reading/uploading data = NSData.read(path, RoboAssets.READ_OPTS); load(data.asByteBuffer(), path.getName(), bufferId); } catch (NSErrorException e) { throw new RuntimeException(e.toString()); } finally { // now dispose the mmap'd file to free up resources if (data != null) { data.dispose(); } } } public static void load(ByteBuffer data, String source, int bufferId) { // read the CAFF metdata to find out the audio format and the data offset/length ByteBuffer buf = data.duplicate().order(ByteOrder.BIG_ENDIAN); if (!getString(buf, 4).equals("caff")) { throw new RuntimeException("Input file not CAFF: " + source); } buf.position(buf.position()+4); // skip rest of caf file header CAFDesc desc = null; int offset = 8, dataOffset = 0, dataLength = 0; do { String type = getString(buf, 4); int size = (int)buf.getLong(); offset += 12; if (type.equals("data")) { // "data" chunk size may be unspecified, in that case it means the rest of file is the // "data" chunk if (size <= 0) { size = buf.limit() - offset; } dataOffset = offset; dataLength = size; } else if (type.equals("desc")) { desc = new CAFDesc(buf); if ("ima4".equalsIgnoreCase(desc.formatID)) throw new RuntimeException("Cannot use compressed CAFF. " + "Use AIFC for compressed audio on iOS."); } offset += size; buf.position(offset); } while (dataOffset == 0); // upload the audio data to OpenAL straight from the buffer data.position(dataOffset); data.limit(dataLength); alBufferData(bufferId, desc.getALFormat(), data, dataLength, (int)desc.sampleRate); // finally freak out if OpenAL didn't like what we sent it int error = alGetError(); if (error != AL_NO_ERROR) { throw new RuntimeException("AL error " + error); } } protected static String getString (ByteBuffer buf, int length) { byte[] data = new byte[length]; buf.get(data); try { return new String(data, "UTF-8"); } catch (UnsupportedEncodingException uee) { throw new RuntimeException(uee); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy