CASampledSP
Loading...
Searching...
No Matches
CACodecInputStream.cpp
Go to the documentation of this file.
1/*
2 * =================================================
3 * Copyright 2011 tagtraum industries incorporated
4 * This file is part of CASampledSP.
5 *
6 * CASampledSP is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * CASampledSP is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with CASampledSP; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 * =================================================
20 *
21 * @author <a href="mailto:hs@tagtraum.com">Hendrik Schreiber</a>
22 */
23#include "com_tagtraum_casampledsp_CACodecInputStream.h"
24#include "CAUtils.h"
25
26static jfieldID nativeBufferFID = NULL;
27static jmethodID rewindMID = NULL;
28static jmethodID limitMID = NULL;
29static jmethodID fillNativeBufferMID = NULL;
30static jmethodID hasRemainingMID = NULL;
31static jmethodID positionMID = NULL;
32
33
37static OSStatus CACodecInputStream_ComplexInputDataProc (
38 AudioConverterRef inAudioConverter,
39 UInt32 *ioNumberDataPackets,
40 AudioBufferList *ioData,
41 AudioStreamPacketDescription **outDataPacketDescription,
42 void *inUserData) {
43
44#ifdef DEBUG
45 fprintf(stderr, "CACodecInputStream_ComplexInputDataProc\n");
46#endif
47
48 int res = 0;
49 CAAudioConverterIO *acio = (CAAudioConverterIO*)inUserData;
50 jobject byteBuffer;
51
52 *ioNumberDataPackets = 0;
53 byteBuffer = acio->env->GetObjectField(acio->sourceStream, nativeBufferFID);
54
55 // check whether we have to fill the source's native buffer
56 if (acio->env->CallBooleanMethod(byteBuffer, hasRemainingMID) == JNI_FALSE) {
57 // fill native buffer
58 acio->env->CallVoidMethod(acio->sourceStream, fillNativeBufferMID);
59 res = acio->env->ExceptionCheck();
60 if (res) {
61 goto bail;
62 }
63 }
64 if (acio->env->CallBooleanMethod(byteBuffer, hasRemainingMID) == JNI_FALSE) {
65 goto bail;
66 }
67
68 // move position in java bytebuffer
69 acio->env->CallIntMethod(byteBuffer, positionMID, acio->sourceAudioIO->srcBufferSize);
70 ioData->mNumberBuffers = 1;
71 ioData->mBuffers[0].mNumberChannels = acio->sourceAudioIO->srcFormat.mChannelsPerFrame;
72 ioData->mBuffers[0].mDataByteSize = acio->sourceAudioIO->srcBufferSize;
73 //ioData->mBuffers[0].mDataByteSize = acio->sourceAudioIO->pktDescs[0].mDataByteSize;
74 ioData->mBuffers[0].mData = acio->sourceAudioIO->srcBuffer;
75 *ioNumberDataPackets = acio->sourceAudioIO->pos - acio->sourceAudioIO->lastPos;
76
77 if (outDataPacketDescription != NULL) {
78 *outDataPacketDescription = acio->sourceAudioIO->pktDescs;
79 }
80 *ioNumberDataPackets = acio->sourceAudioIO->pos - acio->sourceAudioIO -> lastPos;
81
82bail:
83 if (res) {
84 *ioNumberDataPackets = 0;
85 }
86
87 return res;
88}
89
98JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_fillNativeBuffer(JNIEnv *env, jobject stream, jlong converterPtr) {
99
100#ifdef DEBUG
101 fprintf(stderr, "CACodecInputStream fillNativeBuffer\n");
102#endif
103
104 int res = 0;
105 int limit = 0;
106 UInt32 ioOutputDataPacketSize;
107 CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
108 AudioBufferList outOutputData;
109 jobject byteBuffer = NULL;
110 acio->env = env;
111
112 // get java-managed byte buffer reference
113 byteBuffer = env->GetObjectField(stream, nativeBufferFID);
114 if (byteBuffer == NULL) {
115 throwIOExceptionIfError(env, 1, "Failed to get native buffer for this codec");
116 goto bail;
117 }
118
119 // get pointer to our java managed bytebuffer
120 acio->srcBuffer = (char *)env->GetDirectBufferAddress(byteBuffer);
121 acio->srcBufferSize = env->GetDirectBufferCapacity(byteBuffer);
122 if (acio->srcBuffer == NULL) {
123 throwIOExceptionIfError(env, 1, "Failed to obtain native buffer address for this codec");
124 goto bail;
125 }
126 ioOutputDataPacketSize = acio->srcBufferSize/acio->srcFormat.mBytesPerPacket;
127 outOutputData.mNumberBuffers = 1;
128 outOutputData.mBuffers[0].mNumberChannels = acio->srcFormat.mChannelsPerFrame;
129 outOutputData.mBuffers[0].mDataByteSize = acio->srcBufferSize;
130 outOutputData.mBuffers[0].mData = acio->srcBuffer;
131
132 res = AudioConverterFillComplexBuffer(acio->acref,
133 CACodecInputStream_ComplexInputDataProc,
134 acio,
135 &ioOutputDataPacketSize,
136 &outOutputData,
137 acio->pktDescs);
138
139 if (res) {
140 throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to fill complex audio buffer");
141 goto bail;
142 }
143
144 // we already wrote to the buffer, now we still need to
145 // set new bytebuffer limit and position to 0.
146 acio->lastPos = acio->pos;
147 acio->pos += ioOutputDataPacketSize;
148 if (acio->srcFormat.mBytesPerPacket != 0) {
149 limit = ioOutputDataPacketSize*acio->srcFormat.mBytesPerPacket;
150 }
151 else {
152 uint i;
153 for (i=0; i<ioOutputDataPacketSize; i++) {
154 limit += acio->pktDescs[i].mDataByteSize;
155 }
156 }
157 acio->srcBufferSize = limit;
158 env->CallObjectMethod(byteBuffer, limitMID, (jint)limit);
159 env->CallObjectMethod(byteBuffer, rewindMID);
160 if (acio->sourceAudioIO->frameOffset != 0) {
161#ifdef DEBUG
162 fprintf(stderr, "Need to adjust position to frame: %i\n", acio->sourceAudioIO->frameOffset);
163 fprintf(stderr, "acio->srcFormat.mBytesPerFrame : %i\n", acio->srcFormat.mBytesPerFrame);
164 env->CallIntMethod(byteBuffer, positionMID, acio->srcFormat.mBytesPerFrame * acio->sourceAudioIO->frameOffset);
165#endif
166 acio->sourceAudioIO->frameOffset = 0;
167 }
168
169bail:
170 return;
171}
172
183JNIEXPORT jlong JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_open(JNIEnv *env, jobject stream, jobject targetFormat, jobject sourceStream, jlong pointer) {
184 int res = 0;
186 acio->sourceStream = NULL;
187
188 jobject byteBuffer = NULL;
189 jclass audioFormatClass = NULL;
190 jmethodID sampleRateMID = NULL;
191 jmethodID channelsMID = NULL;
192 jmethodID frameSizeMID = NULL;
193 jmethodID sampleSizeInBitsMID = NULL;
194 jmethodID encodingMID = NULL;
195 jmethodID bigEndianMID = NULL;
196
197 jclass caEncodingClass = NULL;
198 jmethodID dataFormatMID = NULL;
199 jobject targetEncoding = NULL;
200
201 /* get method and field ids, if we don't have them already */
202 if (fillNativeBufferMID == NULL || hasRemainingMID == NULL || positionMID == NULL || nativeBufferFID == NULL || rewindMID == NULL || limitMID == NULL) {
203 jclass nativePeerInputStreamClass = env->FindClass("com/tagtraum/casampledsp/CANativePeerInputStream");
204 fillNativeBufferMID = env->GetMethodID(nativePeerInputStreamClass, "fillNativeBuffer", "()V");
205 nativeBufferFID = env->GetFieldID(nativePeerInputStreamClass, "nativeBuffer", "Ljava/nio/ByteBuffer;");
206 jclass bufferClass = env->FindClass("java/nio/Buffer");
207 hasRemainingMID = env->GetMethodID(bufferClass, "hasRemaining", "()Z");
208 positionMID = env->GetMethodID(bufferClass, "position", "(I)Ljava/nio/Buffer;");
209 rewindMID = env->GetMethodID(bufferClass, "rewind", "()Ljava/nio/Buffer;");
210 limitMID = env->GetMethodID(bufferClass, "limit", "(I)Ljava/nio/Buffer;");
211 }
212
213 // get java-managed byte buffer reference
214 byteBuffer = env->GetObjectField(stream, nativeBufferFID);
215 if (byteBuffer == NULL) {
216 throwIOExceptionIfError(env, 1, "Failed to get native buffer for this codec");
217 goto bail;
218 }
219
220 acio->sourceStream = env->NewGlobalRef(sourceStream);
221 acio->sourceAudioIO = (CAAudioIO*)pointer;
222 acio->pktDescs = NULL;
223 acio->env = env;
224 acio->srcBuffer = (char *)env->GetDirectBufferAddress(byteBuffer);
225 acio->srcBufferSize = env->GetDirectBufferCapacity(byteBuffer);
226 acio->cookie = NULL;
227 acio->cookieSize = 0;
228 acio->pos = 0;
229 acio->lastPos = 0;
230 acio->frameOffset = 0;
231
232
233 audioFormatClass = env->FindClass("javax/sound/sampled/AudioFormat");
234 sampleRateMID = env->GetMethodID(audioFormatClass, "getSampleRate", "()F");
235 channelsMID = env->GetMethodID(audioFormatClass, "getChannels", "()I");
236 frameSizeMID = env->GetMethodID(audioFormatClass, "getFrameSize", "()I");
237 sampleSizeInBitsMID = env->GetMethodID(audioFormatClass, "getSampleSizeInBits", "()I");
238 encodingMID = env->GetMethodID(audioFormatClass, "getEncoding", "()Ljavax/sound/sampled/AudioFormat$Encoding;");
239 bigEndianMID = env->GetMethodID(audioFormatClass, "isBigEndian", "()Z");
240
241 caEncodingClass = env->FindClass("com/tagtraum/casampledsp/CAAudioFormat$CAEncoding");
242 dataFormatMID = env->GetMethodID(caEncodingClass, "getDataFormat", "()I");
243 targetEncoding = env->CallObjectMethod(targetFormat, encodingMID);
244
245
246 // set up the format we want to convert *to*
247 acio->srcFormat.mSampleRate = (Float64)env->CallFloatMethod(targetFormat, sampleRateMID);
248 acio->srcFormat.mChannelsPerFrame = (UInt32)env->CallIntMethod(targetFormat, channelsMID);
249 acio->srcFormat.mBitsPerChannel = (UInt32)env->CallIntMethod(targetFormat, sampleSizeInBitsMID);
250 acio->srcFormat.mFramesPerPacket = 1;
251 acio->srcFormat.mBytesPerFrame = (UInt32)env->CallIntMethod(targetFormat, frameSizeMID);
252 acio->srcFormat.mBytesPerPacket = acio->srcFormat.mBytesPerFrame;
253 acio->srcFormat.mFormatID = (UInt32)env->CallIntMethod(targetEncoding, dataFormatMID);
254 acio->srcFormat.mFormatFlags = 0;
255 acio->srcFormat.mReserved = 0;
256
257 // massage format flags
258 if (acio->srcFormat.mFormatID == kAudioFormatLinearPCM) {
259 acio->srcFormat.mFormatFlags += env->CallBooleanMethod(targetFormat, bigEndianMID) == JNI_TRUE ? kAudioFormatFlagIsBigEndian : 0;
260 acio->srcFormat.mFormatFlags += kAudioFormatFlagIsPacked;
261 //acio->srcFormat.mFormatFlags += kAudioFormatFlagIsFloat;
262 // for now we don't support unsigned PCM
263 acio->srcFormat.mFormatFlags += kAudioFormatFlagIsSignedInteger;
264 }
265
266 // make sure that AudioSystem#NOT_SPECIFIED (i.e. -1) is converted to 0.
267 if (acio->srcFormat.mSampleRate < 0) acio->srcFormat.mSampleRate = 0;
268 if (acio->srcFormat.mChannelsPerFrame < 0) acio->srcFormat.mChannelsPerFrame = 0;
269 if (acio->srcFormat.mBitsPerChannel < 0) acio->srcFormat.mBitsPerChannel = 0;
270 if (acio->srcFormat.mBytesPerFrame < 0) acio->srcFormat.mBytesPerFrame = 0;
271 if (acio->srcFormat.mBytesPerPacket < 0) acio->srcFormat.mBytesPerPacket = 0;
272
273
274 // checks - we need to make sure to not divide by zero later on
275 if (acio->srcFormat.mBytesPerFrame == 0) {
276 throwIllegalArgumentExceptionIfError(env, 1, "frameSize must be positive");
277 goto bail;
278 }
279 if (acio->srcFormat.mBytesPerPacket == 0) {
280 throwIllegalArgumentExceptionIfError(env, 1, "bytesPerPacket must be positive");
281 goto bail;
282 }
283 if (acio->srcFormat.mBitsPerChannel == 0) {
284 throwIllegalArgumentExceptionIfError(env, 1, "sampleSizeInBits must be positive");
285 goto bail;
286 }
287
288
289 // setup the format we want to convert *from*
290 while (acio->sourceAudioIO->srcFormat.mFormatID == 0) {
291 // we don't have the native structure yet/anymore - therefore we have to call fillBuffer at least once
292 env->CallVoidMethod(sourceStream, fillNativeBufferMID);
293 res = acio->env->ExceptionCheck();
294 if (res) {
295 goto bail;
296 }
297 }
298
299 res = AudioConverterNew(&acio->sourceAudioIO->srcFormat, &acio->srcFormat, &acio->acref);
300 if (res) {
301 throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to create native codec");
302 goto bail;
303 }
304
305 // TODO: Deal with AudioConverterPrimeInfo as described in ConvertFile sample code.
306
307 /*
308 if (acio->sourceAudioIO->srcFormat.mBytesPerPacket == 0) {
309 // input format is VBR, so we need to get max size per packet
310 UInt32 size = sizeof(acio->srcSizePerPacket);
311 res = AudioConverterGetProperty(acio->acref, kAudioConverterPropertyMaximumInputPacketSize, &size, &acio->srcSizePerPacket);
312 if (res) {
313 throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to get maximum output packet size");
314 goto bail;
315 }
316 acio->srcSizePerPacket = 5793;
317 fprintf(stderr, "acio->srcSizePerPacket : %i\n", acio->srcSizePerPacket);
318 fprintf(stderr, "acio->srcBufferSize : %i\n", acio->srcBufferSize);
319 acio->numPacketsPerRead = acio->srcBufferSize / acio->srcSizePerPacket;
320 //acio->pktDescs = new AudioStreamPacketDescription [acio->numPacketsPerRead];
321 fprintf(stderr, "acio->numPacketsPerRead : %i\n", acio->numPacketsPerRead);
322 }
323 else {
324 acio->srcSizePerPacket = acio->srcFormat.mBytesPerPacket;
325 acio->numPacketsPerRead = acio->srcBufferSize / acio->srcSizePerPacket;
326 }
327 */
328
329 // set cookie, if we have one
330 if (acio->sourceAudioIO->cookieSize > 0) {
331 res = AudioConverterSetProperty(acio->acref, kAudioConverterDecompressionMagicCookie, acio->sourceAudioIO->cookieSize, acio->sourceAudioIO->cookie);
332 if (res) {
333 throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to set cookie from source.");
334 goto bail;
335 }
336 }
337
338bail:
339 if (res) {
340 if (acio->sourceStream != NULL) {
341 env->DeleteGlobalRef(acio->sourceStream);
342 acio->sourceStream = NULL;
343 }
344 if (acio->acref != NULL) {
345 AudioConverterDispose(acio->acref);
346 }
347 if (acio->pktDescs != NULL) {
348 delete acio->pktDescs;
349 }
350 delete acio;
351 }
352
353 return (jlong)acio;
354}
355
363JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_close(JNIEnv *env, jobject stream, jlong converterPtr) {
364 if (converterPtr == 0) return;
365 CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
366 if (acio->sourceStream != NULL) {
367 env->DeleteGlobalRef(acio->sourceStream);
368 acio->sourceStream = NULL;
369 }
370 if (acio->acref != NULL) {
371 int res = AudioConverterDispose(acio->acref);
372 if (res) {
373 throwIOExceptionIfError(env, res, "Failed to close codec");
374 }
375 }
376 if (acio->pktDescs != NULL) {
377 delete acio->pktDescs;
378 }
379 delete acio;
380
381}
382
390JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_reset(JNIEnv *env, jobject stream, jlong converterPtr) {
391 if (converterPtr == 0) return;
392 CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
393 if (acio->acref != NULL) {
394 int res = AudioConverterReset(acio->acref);
395 if (res) {
396 throwIOExceptionIfError(env, res, "Failed to reset audio converter");
397 }
398 } else {
399 throwIOExceptionIfError(env, -1, "Failed to reset audio converter as it is NULL");
400 }
401}
402
JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_reset(JNIEnv *env, jobject stream, jlong converterPtr)
Resets the converter - necessary after seek() to flush codec buffers.
JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_fillNativeBuffer(JNIEnv *env, jobject stream, jlong converterPtr)
Fill this streams native buffer by transcoding the content of the source stream's native buffer to th...
JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_close(JNIEnv *env, jobject stream, jlong converterPtr)
Closes the AudioConverter and cleans up other resources.
JNIEXPORT jlong JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_open(JNIEnv *env, jobject stream, jobject targetFormat, jobject sourceStream, jlong pointer)
Sets up an AudioConverter to convert data to the desired format.
void throwIllegalArgumentExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an IllegalArgumentException.
Definition CAUtils.cpp:73
void throwUnsupportedAudioFileExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an UnsupportedAudioFileException exception.
Definition CAUtils.cpp:39
void throwIOExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an IOException.
Definition CAUtils.cpp:56
Central context representing the native peer to the Java CACodecInputStream object.
Definition CAUtils.h:68
jobject sourceStream
Source stream object (Java)
Definition CAUtils.h:70
AudioConverterRef acref
The used AudioConverter.
Definition CAUtils.h:69
CAAudioIO * sourceAudioIO
CAAudioIO of the stream that we want to convert.
Definition CAUtils.h:71
Central context representing the native peer to the Java CANativePeerInputStream object.
Definition CAUtils.h:48
JNIEnv * env
JNI environment.
Definition CAUtils.h:49
char * cookie
Cookie.
Definition CAUtils.h:60
SInt64 lastPos
Last position (in packets)
Definition CAUtils.h:57
char * srcBuffer
Source buffer.
Definition CAUtils.h:53
UInt32 frameOffset
Frame offset (needed for seeking to the middles of a packet)
Definition CAUtils.h:58
AudioStreamBasicDescription srcFormat
Source format.
Definition CAUtils.h:51
UInt32 cookieSize
Cookie size.
Definition CAUtils.h:61
SInt64 pos
Current position (in packets)
Definition CAUtils.h:56
UInt32 srcBufferSize
Source buffer size.
Definition CAUtils.h:54
AudioStreamPacketDescription * pktDescs
Packet descriptions.
Definition CAUtils.h:52