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
81bail:
82 if (res) {
83 *ioNumberDataPackets = 0;
84 }
85
86 return res;
87}
88
97JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_fillNativeBuffer(JNIEnv *env, jobject stream, jlong converterPtr) {
98
99#ifdef DEBUG
100 fprintf(stderr, "CACodecInputStream fillNativeBuffer\n");
101#endif
102
103 int res = 0;
104 int limit = 0;
105 UInt32 ioOutputDataPacketSize;
106 CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
107 AudioBufferList outOutputData;
108 jobject byteBuffer = NULL;
109 acio->env = env;
110
111 // get java-managed byte buffer reference
112 byteBuffer = env->GetObjectField(stream, nativeBufferFID);
113 if (byteBuffer == NULL) {
114 throwIOExceptionIfError(env, 1, "Failed to get native buffer for this codec");
115 goto bail;
116 }
117
118 // get pointer to our java managed bytebuffer
119 acio->srcBuffer = (char *)env->GetDirectBufferAddress(byteBuffer);
120 acio->srcBufferSize = env->GetDirectBufferCapacity(byteBuffer);
121 if (acio->srcBuffer == NULL) {
122 throwIOExceptionIfError(env, 1, "Failed to obtain native buffer address for this codec");
123 goto bail;
124 }
125 ioOutputDataPacketSize = acio->srcBufferSize/acio->srcFormat.mBytesPerPacket;
126 outOutputData.mNumberBuffers = 1;
127 outOutputData.mBuffers[0].mNumberChannels = acio->srcFormat.mChannelsPerFrame;
128 outOutputData.mBuffers[0].mDataByteSize = acio->srcBufferSize;
129 outOutputData.mBuffers[0].mData = acio->srcBuffer;
130
131 res = AudioConverterFillComplexBuffer(acio->acref,
132 CACodecInputStream_ComplexInputDataProc,
133 acio,
134 &ioOutputDataPacketSize,
135 &outOutputData,
136 acio->pktDescs);
137
138 if (res) {
139 throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to fill complex audio buffer");
140 goto bail;
141 }
142
143 // we already wrote to the buffer, now we still need to
144 // set new bytebuffer limit and position to 0.
145 acio->lastPos = acio->pos;
146 acio->pos += ioOutputDataPacketSize;
147 if (acio->srcFormat.mBytesPerPacket != 0) {
148 limit = ioOutputDataPacketSize*acio->srcFormat.mBytesPerPacket;
149 }
150 else {
151 uint i;
152 for (i=0; i<ioOutputDataPacketSize; i++) {
153 limit += acio->pktDescs[i].mDataByteSize;
154 }
155 }
156 acio->srcBufferSize = limit;
157 env->CallObjectMethod(byteBuffer, limitMID, (jint)limit);
158 env->CallObjectMethod(byteBuffer, rewindMID);
159 if (acio->sourceAudioIO->frameOffset != 0) {
160#ifdef DEBUG
161 fprintf(stderr, "Need to adjust position to frame: %i\n", acio->sourceAudioIO->frameOffset);
162 fprintf(stderr, "acio->srcFormat.mBytesPerFrame : %i\n", acio->srcFormat.mBytesPerFrame);
163 env->CallIntMethod(byteBuffer, positionMID, acio->srcFormat.mBytesPerFrame * acio->sourceAudioIO->frameOffset);
164#endif
165 acio->sourceAudioIO->frameOffset = 0;
166 }
167
168bail:
169 return;
170}
171
182JNIEXPORT jlong JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_open(JNIEnv *env, jobject stream, jobject targetFormat, jobject sourceStream, jlong pointer) {
183 int res = 0;
185 acio->sourceStream = NULL;
186
187 jobject byteBuffer = NULL;
188 jclass audioFormatClass = NULL;
189 jmethodID sampleRateMID = NULL;
190 jmethodID channelsMID = NULL;
191 jmethodID frameSizeMID = NULL;
192 jmethodID sampleSizeInBitsMID = NULL;
193 jmethodID encodingMID = NULL;
194 jmethodID bigEndianMID = NULL;
195
196 jclass caEncodingClass = NULL;
197 jmethodID dataFormatMID = NULL;
198 jobject targetEncoding = NULL;
199
200 /* get method and field ids, if we don't have them already */
201 if (fillNativeBufferMID == NULL || hasRemainingMID == NULL || positionMID == NULL || nativeBufferFID == NULL || rewindMID == NULL || limitMID == NULL) {
202 jclass nativePeerInputStreamClass = env->FindClass("com/tagtraum/casampledsp/CANativePeerInputStream");
203 fillNativeBufferMID = env->GetMethodID(nativePeerInputStreamClass, "fillNativeBuffer", "()V");
204 nativeBufferFID = env->GetFieldID(nativePeerInputStreamClass, "nativeBuffer", "Ljava/nio/ByteBuffer;");
205 jclass bufferClass = env->FindClass("java/nio/Buffer");
206 hasRemainingMID = env->GetMethodID(bufferClass, "hasRemaining", "()Z");
207 positionMID = env->GetMethodID(bufferClass, "position", "(I)Ljava/nio/Buffer;");
208 rewindMID = env->GetMethodID(bufferClass, "rewind", "()Ljava/nio/Buffer;");
209 limitMID = env->GetMethodID(bufferClass, "limit", "(I)Ljava/nio/Buffer;");
210 }
211
212 // get java-managed byte buffer reference
213 byteBuffer = env->GetObjectField(stream, nativeBufferFID);
214 if (byteBuffer == NULL) {
215 throwIOExceptionIfError(env, 1, "Failed to get native buffer for this codec");
216 goto bail;
217 }
218
219 acio->sourceStream = env->NewGlobalRef(sourceStream);
220 acio->sourceAudioIO = (CAAudioIO*)pointer;
221 acio->pktDescs = NULL;
222 acio->env = env;
223 acio->srcBuffer = (char *)env->GetDirectBufferAddress(byteBuffer);
224 acio->srcBufferSize = env->GetDirectBufferCapacity(byteBuffer);
225 acio->cookie = NULL;
226 acio->cookieSize = 0;
227 acio->pos = 0;
228 acio->lastPos = 0;
229 acio->frameOffset = 0;
230
231
232 audioFormatClass = env->FindClass("javax/sound/sampled/AudioFormat");
233 sampleRateMID = env->GetMethodID(audioFormatClass, "getSampleRate", "()F");
234 channelsMID = env->GetMethodID(audioFormatClass, "getChannels", "()I");
235 frameSizeMID = env->GetMethodID(audioFormatClass, "getFrameSize", "()I");
236 sampleSizeInBitsMID = env->GetMethodID(audioFormatClass, "getSampleSizeInBits", "()I");
237 encodingMID = env->GetMethodID(audioFormatClass, "getEncoding", "()Ljavax/sound/sampled/AudioFormat$Encoding;");
238 bigEndianMID = env->GetMethodID(audioFormatClass, "isBigEndian", "()Z");
239
240 caEncodingClass = env->FindClass("com/tagtraum/casampledsp/CAAudioFormat$CAEncoding");
241 dataFormatMID = env->GetMethodID(caEncodingClass, "getDataFormat", "()I");
242 targetEncoding = env->CallObjectMethod(targetFormat, encodingMID);
243
244
245 // set up the format we want to convert *to*
246 acio->srcFormat.mSampleRate = (Float64)env->CallFloatMethod(targetFormat, sampleRateMID);
247 acio->srcFormat.mChannelsPerFrame = (UInt32)env->CallIntMethod(targetFormat, channelsMID);
248 acio->srcFormat.mBitsPerChannel = (UInt32)env->CallIntMethod(targetFormat, sampleSizeInBitsMID);
249 acio->srcFormat.mFramesPerPacket = 1;
250 acio->srcFormat.mBytesPerFrame = (UInt32)env->CallIntMethod(targetFormat, frameSizeMID);
251 acio->srcFormat.mBytesPerPacket = acio->srcFormat.mBytesPerFrame;
252 acio->srcFormat.mFormatID = (UInt32)env->CallIntMethod(targetEncoding, dataFormatMID);
253 acio->srcFormat.mFormatFlags = 0;
254 acio->srcFormat.mReserved = 0;
255
256 // massage format flags
257 if (acio->srcFormat.mFormatID == kAudioFormatLinearPCM) {
258 acio->srcFormat.mFormatFlags += env->CallBooleanMethod(targetFormat, bigEndianMID) == JNI_TRUE ? kAudioFormatFlagIsBigEndian : 0;
259 acio->srcFormat.mFormatFlags += kAudioFormatFlagIsPacked;
260 //acio->srcFormat.mFormatFlags += kAudioFormatFlagIsFloat;
261 // for now we don't support unsigned PCM
262 acio->srcFormat.mFormatFlags += kAudioFormatFlagIsSignedInteger;
263 }
264
265 // make sure that AudioSystem#NOT_SPECIFIED (i.e. -1) is converted to 0.
266 if (acio->srcFormat.mSampleRate < 0) acio->srcFormat.mSampleRate = 0;
267 if (acio->srcFormat.mChannelsPerFrame < 0) acio->srcFormat.mChannelsPerFrame = 0;
268 if (acio->srcFormat.mBitsPerChannel < 0) acio->srcFormat.mBitsPerChannel = 0;
269 if (acio->srcFormat.mBytesPerFrame < 0) acio->srcFormat.mBytesPerFrame = 0;
270 if (acio->srcFormat.mBytesPerPacket < 0) acio->srcFormat.mBytesPerPacket = 0;
271
272
273 // checks - we need to make sure to not divide by zero later on
274 if (acio->srcFormat.mBytesPerFrame == 0) {
275 throwIllegalArgumentExceptionIfError(env, 1, "frameSize must be positive");
276 goto bail;
277 }
278 if (acio->srcFormat.mBytesPerPacket == 0) {
279 throwIllegalArgumentExceptionIfError(env, 1, "bytesPerPacket must be positive");
280 goto bail;
281 }
282 if (acio->srcFormat.mBitsPerChannel == 0) {
283 throwIllegalArgumentExceptionIfError(env, 1, "sampleSizeInBits must be positive");
284 goto bail;
285 }
286
287
288 // setup the format we want to convert *from*
289 while (acio->sourceAudioIO->srcFormat.mFormatID == 0) {
290 // we don't have the native structure yet/anymore - therefore we have to call fillBuffer at least once
291 env->CallVoidMethod(sourceStream, fillNativeBufferMID);
292 res = acio->env->ExceptionCheck();
293 if (res) {
294 goto bail;
295 }
296 }
297
298 res = AudioConverterNew(&acio->sourceAudioIO->srcFormat, &acio->srcFormat, &acio->acref);
299 if (res) {
300 throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to create native codec");
301 goto bail;
302 }
303
304 // TODO: Deal with AudioConverterPrimeInfo as described in ConvertFile sample code.
305
306 /*
307 if (acio->sourceAudioIO->srcFormat.mBytesPerPacket == 0) {
308 // input format is VBR, so we need to get max size per packet
309 UInt32 size = sizeof(acio->srcSizePerPacket);
310 res = AudioConverterGetProperty(acio->acref, kAudioConverterPropertyMaximumInputPacketSize, &size, &acio->srcSizePerPacket);
311 if (res) {
312 throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to get maximum output packet size");
313 goto bail;
314 }
315 acio->srcSizePerPacket = 5793;
316 fprintf(stderr, "acio->srcSizePerPacket : %i\n", acio->srcSizePerPacket);
317 fprintf(stderr, "acio->srcBufferSize : %i\n", acio->srcBufferSize);
318 acio->numPacketsPerRead = acio->srcBufferSize / acio->srcSizePerPacket;
319 //acio->pktDescs = new AudioStreamPacketDescription [acio->numPacketsPerRead];
320 fprintf(stderr, "acio->numPacketsPerRead : %i\n", acio->numPacketsPerRead);
321 }
322 else {
323 acio->srcSizePerPacket = acio->srcFormat.mBytesPerPacket;
324 acio->numPacketsPerRead = acio->srcBufferSize / acio->srcSizePerPacket;
325 }
326 */
327
328 // set cookie, if we have one
329 if (acio->sourceAudioIO->cookieSize > 0) {
330 res = AudioConverterSetProperty(acio->acref, kAudioConverterDecompressionMagicCookie, acio->sourceAudioIO->cookieSize, acio->sourceAudioIO->cookie);
331 if (res) {
332 throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to set cookie from source.");
333 goto bail;
334 }
335 }
336
337bail:
338 if (res) {
339 if (acio->sourceStream != NULL) {
340 env->DeleteGlobalRef(acio->sourceStream);
341 acio->sourceStream = NULL;
342 }
343 if (acio->acref != NULL) {
344 AudioConverterDispose(acio->acref);
345 }
346 if (acio->pktDescs != NULL) {
347 delete acio->pktDescs;
348 }
349 delete acio;
350 }
351
352 return (jlong)acio;
353}
354
362JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_close(JNIEnv *env, jobject stream, jlong converterPtr) {
363 if (converterPtr == 0) return;
364 CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
365 if (acio->sourceStream != NULL) {
366 env->DeleteGlobalRef(acio->sourceStream);
367 acio->sourceStream = NULL;
368 }
369 if (acio->acref != NULL) {
370 int res = AudioConverterDispose(acio->acref);
371 if (res) {
372 throwIOExceptionIfError(env, res, "Failed to close codec");
373 }
374 }
375 if (acio->pktDescs != NULL) {
376 delete acio->pktDescs;
377 }
378 delete acio;
379
380}
381
389JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_reset(JNIEnv *env, jobject stream, jlong converterPtr) {
390 if (converterPtr == 0) return;
391 CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
392 if (acio->acref != NULL) {
393 int res = AudioConverterReset(acio->acref);
394 if (res) {
395 throwIOExceptionIfError(env, res, "Failed to reset audio converter");
396 }
397 } else {
398 throwIOExceptionIfError(env, -1, "Failed to reset audio converter as it is NULL");
399 }
400}
401
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