FFSampledSP
Loading...
Searching...
No Matches
FFAudioFileReader.c
Go to the documentation of this file.
1/*
2 * =================================================
3 * Copyright 2013 tagtraum industries incorporated
4 * This file is part of FFSampledSP.
5 *
6 * FFSampledSP 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 * FFSampledSP 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 FFSampledSP; 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
24#include "FFUtils.h"
26
27static const int CALLBACK_BUFFERSIZE = 32 * 1024;
28
29static jmethodID limit_MID = NULL;
30static jmethodID ffAudioFileFormat_MID = NULL;
31
43 JNIEnv *env, jclass readerClass) {
44#ifdef DEBUG
45 fprintf(stderr, "FFAudioFileReader.c initIDs(env)\n");
46#endif
47
48 jclass bufferClass = (*env)->FindClass(env, "java/nio/Buffer");
49 if (!bufferClass) {
50#ifdef DEBUG
51 fprintf(stderr, "Failed to find java/nio/Buffer\n");
52#endif
53 return;
54 }
55 jclass ffAudioFileFormat_class =
56 (*env)->FindClass(env, "com/tagtraum/ffsampledsp/FFAudioFileFormat");
57 if (!ffAudioFileFormat_class) {
58#ifdef DEBUG
59 fprintf(stderr,
60 "Failed to find com/tagtraum/ffsampledsp/FFAudioFileFormat\n");
61#endif
62 return;
63 }
64
65 limit_MID = (*env)->GetMethodID(env, bufferClass, "limit", "()I");
66 if (!limit_MID) {
67#ifdef DEBUG
68 fprintf(stderr, "Failed to find limit method\n");
69#endif
70 return;
71 }
72 ffAudioFileFormat_MID = (*env)->GetMethodID(
73 env, ffAudioFileFormat_class, "<init>",
74 "(Ljava/lang/String;IFIIIFIZJILjava/lang/Boolean;Z)V");
75#ifdef DEBUG
76 if (!ffAudioFileFormat_MID)
77 fprintf(stderr, "Failed to find constructor of AudioFileFormat\n");
78#endif
79}
80
84typedef struct {
85 JNIEnv *env;
86 jobject byte_buffer;
89
99static int read_callback(void *opaque, uint8_t *buf, int size) {
100 int res = 0;
101 int availableData;
102 uint8_t *java_buffer = NULL;
103 FFCallback *callback = (FFCallback *)opaque;
104
105 if (callback->call_count > 0) {
106 res = AVERROR_EOF;
107 goto bail;
108 }
109 callback->call_count = 1;
110
111 java_buffer =
112 (uint8_t *)(*callback->env)
113 ->GetDirectBufferAddress(callback->env, callback->byte_buffer);
114 if (!java_buffer) {
115 res = -1;
116 throwIOExceptionIfError(callback->env, 1,
117 "Failed to get address for byte buffer");
118 goto bail;
119 }
120 // get available data, i.e. the limit of the java buffer
121 availableData =
122 (*callback->env)
123 ->CallIntMethod(callback->env, callback->byte_buffer, limit_MID);
124 if ((*callback->env)->ExceptionCheck(callback->env)) {
125 res = AVERROR(EIO);
126 goto bail;
127 }
128 // never write past the end of buf
129 if (availableData > size)
130 availableData = size;
131 // copy to c buf
132 memcpy(buf, (const uint8_t *)java_buffer, availableData);
133
134 res = availableData;
135
136bail:
137
138 return res;
139}
140
148static int is_pcm(enum AVCodecID id) {
149 int res = 0;
150 switch (id) {
151 case AV_CODEC_ID_PCM_S8:
152 case AV_CODEC_ID_PCM_U8:
153 case AV_CODEC_ID_PCM_S16BE:
154 case AV_CODEC_ID_PCM_S16LE:
155 case AV_CODEC_ID_PCM_U16BE:
156 case AV_CODEC_ID_PCM_U16LE:
157 case AV_CODEC_ID_PCM_S24BE:
158 case AV_CODEC_ID_PCM_S24LE:
159 case AV_CODEC_ID_PCM_S32BE:
160 case AV_CODEC_ID_PCM_S32LE:
161 case AV_CODEC_ID_PCM_U32BE:
162 case AV_CODEC_ID_PCM_U32LE:
163 case AV_CODEC_ID_PCM_F32BE:
164 case AV_CODEC_ID_PCM_F32LE:
165 case AV_CODEC_ID_PCM_F64BE:
166 case AV_CODEC_ID_PCM_F64LE:
167 case AV_CODEC_ID_PCM_F16LE:
168 case AV_CODEC_ID_PCM_F24LE:
169 res = 1;
170#ifdef DEBUG
171 fprintf(stderr, "is PCM\n");
172#endif
173 break;
174 default:
175 res = 0;
176#ifdef DEBUG
177 fprintf(stderr, "is NOT PCM\n");
178#endif
179 }
180 return res;
181}
182
190static jlong duration(AVFormatContext *format_context, AVStream *stream) {
191 jlong duration_in_microseconds = -1;
192
193 if (format_context->duration != AV_NOPTS_VALUE) {
194 int64_t micro_seconds_base = AV_TIME_BASE / 1000000; // this should be=1
195 duration_in_microseconds =
196 (jlong)(format_context->duration / micro_seconds_base);
197#ifdef DEBUG
198 fprintf(stderr, "format_context->duration: %i\n", format_context->duration);
199 fprintf(stderr, "duration_in_microseconds 0: %li\n",
200 duration_in_microseconds);
201#endif
202 }
203
204 if (stream->nb_frames != 0 && duration_in_microseconds <= 0 &&
205 stream->codecpar->sample_rate > 0) {
206 duration_in_microseconds =
207 stream->nb_frames * 1000000L / stream->codecpar->sample_rate;
208#ifdef DEBUG
209 fprintf(stderr, "stream->nb_frames: %i\n", stream->nb_frames);
210 fprintf(stderr, "stream->codecpar->sample_rate: %f\n",
211 stream->codecpar->sample_rate);
212 fprintf(stderr, "duration_in_microseconds 1: %i\n",
213 duration_in_microseconds);
214#endif
215 }
216#ifdef DEBUG
217 fprintf(stderr, "duration_in_microseconds final: %i\n",
218 duration_in_microseconds);
219#endif
220 return duration_in_microseconds;
221}
222
230static jfloat get_frame_rate(AVStream *stream, jlong duration) {
231 jfloat frame_rate = -1;
232
233 if (frame_rate <= 0 && stream->nb_frames > 0 && duration > 0) {
234 frame_rate = stream->nb_frames * 1000000LL / (jfloat)duration;
235 }
236
237 if (frame_rate <= 0 && stream->codecpar->frame_size > 0 &&
238 stream->codecpar->sample_rate > 0) {
239 frame_rate = (jfloat)stream->codecpar->sample_rate /
240 (jfloat)stream->codecpar->frame_size;
241 }
242
243 if (frame_rate <= 0 && stream->codecpar->frame_size == 0 &&
244 is_pcm(stream->codecpar->codec_id) && stream->codecpar->sample_rate > 0) {
245 frame_rate = (jfloat)stream->codecpar->sample_rate;
246 }
247
248#ifdef DEBUG
249 if (stream->nb_frames > 0 && duration > 0) {
250 fprintf(stderr, "1 frame rate : %f\n",
251 stream->nb_frames * 1000000LL / (jfloat)duration);
252 }
253 if (stream->codecpar->frame_size > 0 && stream->codecpar->sample_rate > 0) {
254 fprintf(stderr, "2 frame rate : %f\n",
255 (jfloat)stream->codecpar->sample_rate /
256 (jfloat)stream->codecpar->frame_size);
257 fprintf(stderr, "frame_size : %f, sample_rate : %f\n",
258 (jfloat)stream->codecpar->frame_size,
259 (jfloat)stream->codecpar->sample_rate);
260 }
261 if (stream->codecpar->frame_size == 0 && is_pcm(stream->codecpar->codec_id) &&
262 stream->codecpar->sample_rate > 0) {
263 fprintf(stderr, "3 frame rate : %f\n",
264 (jfloat)stream->codecpar->sample_rate);
265 }
266#endif
267
268 return frame_rate;
269}
270
278static jobject create_ffaudiofileformat(
279 JNIEnv *env, jclass ffAudioFileFormat_class, jstring url, jint codecId,
280 jfloat sampleRate, jint sampleSize, jint channels, jint frame_size,
281 jfloat frame_rate, jint frame_length, jboolean big_endian, jlong duration,
282 jint bitRate, jobject vbr, jboolean encrypted) {
283 /* Construct an FFAudioFileFormat object */
284 return (*env)->NewObject(env, ffAudioFileFormat_class, ffAudioFileFormat_MID,
285 url, codecId, sampleRate, sampleSize, channels,
286 frame_size, frame_rate, frame_length, big_endian,
287 duration, bitRate, vbr, encrypted);
288}
289
290static int create_ffaudiofileformats(JNIEnv *env,
291 AVFormatContext *format_context,
292 jobjectArray *array, jstring url) {
293 int res = 0;
294 int pcm = 0;
295 jlong duration_in_microseconds = -1;
296 jfloat frame_rate = -1;
297 jint frame_size = -1;
298 jint sample_size = -1;
299 jint frame_length = -1;
300 int audio_stream_count = 0;
301 int audio_stream_number = 0;
302 jboolean encrypted = 0;
303 jobject vbr = NULL;
304 jboolean big_endian = 1;
305 jobject audio_format = NULL;
306 jclass audioFileFormat_class = NULL;
307 jclass ffAudioFileFormat_class = NULL;
308
309 // count possible audio streams
310 int i;
311 for (i = 0; i < format_context->nb_streams; i++) {
312 AVStream *stream = format_context->streams[i];
313 if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
314 audio_stream_count++;
315 }
316 }
317
318#ifdef DEBUG
319 fprintf(stderr, "Found %i audio streams.\n", audio_stream_count);
320#endif
321
322 // are there any audio streams at all?
323 if (audio_stream_count == 0) {
325 "Failed to find audio stream");
326 goto bail;
327 }
328
329 // look up classes once for all streams
330 audioFileFormat_class =
331 (*env)->FindClass(env, "javax/sound/sampled/AudioFileFormat");
332 if (!audioFileFormat_class) {
333 res = -1;
334 goto bail;
335 }
336 ffAudioFileFormat_class =
337 (*env)->FindClass(env, "com/tagtraum/ffsampledsp/FFAudioFileFormat");
338 if (!ffAudioFileFormat_class) {
339 res = -1;
340 goto bail;
341 }
342
343 // create output array
344 *array = (*env)->NewObjectArray(env, audio_stream_count,
345 audioFileFormat_class, NULL);
346 if (*array == NULL) {
347 res = -1;
348 goto bail;
349 }
350
351#ifdef DEBUG
352 fprintf(stderr, "Created audio file format array.\n");
353#endif
354
355 // iterate over audio streams
356 for (i = 0; i < format_context->nb_streams; i++) {
357 AVStream *stream = format_context->streams[i];
358 if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
359 // reset values that are only set conditionally below,
360 // so they don't carry over from a previous stream
361 frame_size = -1;
362 frame_length = -1;
363
364 // create object
365 duration_in_microseconds = duration(format_context, stream);
366 frame_rate = get_frame_rate(stream, duration_in_microseconds);
367 big_endian = ff_big_endian(stream->codecpar->codec_id);
368 pcm = is_pcm(stream->codecpar->codec_id);
369
370 if (pcm) {
371 frame_size = (stream->codecpar->bits_per_coded_sample / 8) *
372 stream->codecpar->ch_layout.nb_channels;
373 }
374
375 // get frame_length, if available and perhaps use it to adjust duration
376 if (stream->nb_frames > 0) {
377 frame_length = stream->nb_frames;
378 } else if (duration_in_microseconds > 0 && pcm &&
379 stream->codecpar->sample_rate > 0) {
380 frame_length =
381 (jint)round(duration_in_microseconds *
382 stream->codecpar->sample_rate / 1000. / 1000.);
383 } else if (duration_in_microseconds > 0 &&
384 stream->codecpar->frame_size > 0 &&
385 stream->codecpar->sample_rate > 0) {
386 frame_length = (jint)round(
387 duration_in_microseconds * stream->codecpar->sample_rate /
388 stream->codecpar->frame_size / 1000. / 1000.);
389 // re-estimat duration, based on non-PCM frame_size
390 duration_in_microseconds = (jlong)(1000. * 1000. * frame_length *
391 stream->codecpar->frame_size /
392 stream->codecpar->sample_rate);
393 }
394
395 // TODO: Support VBR.
396
397 sample_size = stream->codecpar->bits_per_raw_sample
398 ? stream->codecpar->bits_per_raw_sample
399 : stream->codecpar->bits_per_coded_sample;
400 sample_size = sample_size <= 0 ? -1 : sample_size;
401
402 encrypted = stream->codecpar->codec_tag == CODEC_TAG_DRMS;
403
404#ifdef DEBUG
405 fprintf(stderr, "stream->codecpar->bits_per_coded_sample: %i\n",
406 stream->codecpar->bits_per_coded_sample);
407 fprintf(stderr, "stream->codecpar->bits_per_raw_sample : %i\n",
408 stream->codecpar->bits_per_raw_sample);
409 fprintf(stderr, "stream->codecpar->bit_rate : %lli\n",
410 stream->codecpar->bit_rate);
411 fprintf(stderr, "stream->codecpar->codec_tag=drms : %i\n",
412 encrypted);
413 fprintf(stderr, "format_context->packet_size : %i\n",
414 format_context->packet_size);
415 fprintf(stderr, "sample_rate : %i\n", stream->codecpar->sample_rate);
416 fprintf(stderr, "sampleSize : %i\n", sample_size);
417 fprintf(stderr, "channels : %i\n",
418 stream->codecpar->ch_layout.nb_channels);
419 fprintf(stderr, "frame_size : %i\n", (int)frame_size);
420 fprintf(stderr, "frame_rate : %f\n", frame_rate);
421 fprintf(stderr, "frame_length: %i\n", frame_length);
422 fprintf(stderr, "codec_id : %i\n", stream->codecpar->codec_id);
423 fprintf(stderr, "duration : %li\n", duration_in_microseconds);
424 if (big_endian) {
425 fprintf(stderr, "big_endian : true\n");
426 } else {
427 fprintf(stderr, "big_endian : false\n");
428 }
429#endif
430
431 audio_format = create_ffaudiofileformat(
432 env, ffAudioFileFormat_class, url, stream->codecpar->codec_id,
433 (jfloat)stream->codecpar->sample_rate, sample_size,
434 stream->codecpar->ch_layout.nb_channels, frame_size, frame_rate,
435 frame_length, big_endian, duration_in_microseconds,
436 stream->codecpar->bit_rate, vbr, encrypted);
437 if (!audio_format) {
438 res = -1;
439 goto bail;
440 }
441
442 (*env)->SetObjectArrayElement(env, *array, audio_stream_number,
443 audio_format);
444 // don't accumulate local refs for files with many streams
445 (*env)->DeleteLocalRef(env, audio_format);
446 audio_stream_number++;
447 }
448 }
449
450bail:
451 if (res < 0 && *array) {
452 // a Java exception is pending, don't hand out a partially filled array
453 (*env)->DeleteLocalRef(env, *array);
454 *array = NULL;
455 }
456 if (ffAudioFileFormat_class)
457 (*env)->DeleteLocalRef(env, ffAudioFileFormat_class);
458 if (audioFileFormat_class)
459 (*env)->DeleteLocalRef(env, audioFileFormat_class);
460 return res;
461}
462
471JNIEXPORT jobjectArray JNICALL
473 JNIEnv *env, jobject instance, jstring url, jbyteArray urlBytes) {
474
475#ifdef DEBUG
476 fprintf(stderr, "openFromUrl_1\n");
477#endif
478
479 int res = 0;
480 AVFormatContext *format_context = NULL;
481 jobjectArray array = NULL;
482 char *input_url = NULL;
483
484 {
485 jsize urlLen = (*env)->GetArrayLength(env, urlBytes);
486 jbyte *urlBuf = (*env)->GetByteArrayElements(env, urlBytes, NULL);
487 input_url = (char *)malloc(urlLen + 1);
488 if (!input_url) {
489 (*env)->ReleaseByteArrayElements(env, urlBytes, urlBuf, JNI_ABORT);
490 throwIOExceptionIfError(env, AVERROR(ENOMEM),
491 "Failed to allocate URL buffer");
492 goto bail;
493 }
494 memcpy(input_url, urlBuf, urlLen);
495 input_url[urlLen] = '\0';
496 (*env)->ReleaseByteArrayElements(env, urlBytes, urlBuf, JNI_ABORT);
497 }
498
499 res = ff_open_format_context(env, &format_context, input_url, 0);
500 if (res) {
501 goto bail;
502 }
503
504 res = create_ffaudiofileformats(env, format_context, &array, url);
505 if (res) {
506 goto bail;
507 }
508
509bail:
510 if (format_context) {
511 avformat_close_input(&format_context);
512 }
513 free(input_url);
514
515 return array;
516}
517
526JNIEXPORT jobjectArray JNICALL
528 JNIEnv *env, jobject instance, jobject byte_buffer) {
529 int res = 0;
530 AVFormatContext *format_context = NULL;
531 jobjectArray array = NULL;
532
533 unsigned char *callbackBuffer = NULL;
534 FFCallback *callback = NULL;
535 AVIOContext *io_context;
536
537 callback = calloc(1, sizeof(FFCallback));
538 if (!callback) {
539 res = AVERROR(ENOMEM);
540 throwIOExceptionIfError(env, res, "Could not allocate callback.");
541 goto bail;
542 }
543 callback->env = env;
544 callback->byte_buffer = byte_buffer;
545 callback->call_count = 0;
546
547 format_context = avformat_alloc_context();
548 if (!format_context) {
549 res = AVERROR(ENOMEM);
550 throwIOExceptionIfError(env, res, "Could not allocate format context.");
551 goto bail;
552 }
553
554 // limit probe to less than what we read in one chunk...
555 format_context->probesize = 8 * 1024; // this corresponds to the Java code!
556 format_context->max_analyze_duration = 5 * AV_TIME_BASE;
557
558 callbackBuffer =
559 (unsigned char *)av_malloc(CALLBACK_BUFFERSIZE * sizeof(uint8_t));
560 if (!callbackBuffer) {
561 res = AVERROR(ENOMEM);
562 throwIOExceptionIfError(env, res, "Could not allocate callback buffer.");
563 goto bail;
564 }
565
566 io_context = avio_alloc_context(
567 callbackBuffer, // IOBuffer
568 CALLBACK_BUFFERSIZE, // Buffer Size (32kb corresponds to Java code)
569 0, // Write flag, only reading, so 0
570 callback, // FFCallback pointer (opaque)
571 read_callback, // Read callback
572 NULL, // Write callback
573 NULL // Seek callback
574 );
575 if (!io_context) {
576 av_free(callbackBuffer);
577 callbackBuffer = NULL;
578 res = AVERROR(ENOMEM);
579 throwIOExceptionIfError(env, res, "Could not allocate custom IO context.");
580 goto bail;
581 }
582 // we didn't supply a seek function in avio_alloc_context, so we need to make
583 // sure we don't seek..
584 io_context->seekable = 0;
585
586 format_context->pb = io_context;
587
588 res = ff_open_format_context(env, &format_context, "MemoryAVIOContext", 0);
589 if (res) {
590 goto bail;
591 }
592
593 res = create_ffaudiofileformats(env, format_context, &array, NULL);
594 if (res) {
595 goto bail;
596 }
597
598bail:
599
600 if (format_context) {
601 AVFormatContext *s = format_context;
602 if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
603 (s->flags & AVFMT_FLAG_CUSTOM_IO)) {
604 if (s->pb) {
605 avio_flush(s->pb);
606 avio_context_free(&s->pb);
607 }
608 }
609
610 avformat_close_input(&format_context);
611 }
612 if (callback) {
613 free(callback);
614 }
615
616 return array;
617}
JNIEXPORT void JNICALL Java_com_tagtraum_ffsampledsp_FFAudioFileReader_initIDs(JNIEnv *env, jclass readerClass)
Init static method and field ids for Java methods/fields.
JNIEXPORT jobjectArray JNICALL Java_com_tagtraum_ffsampledsp_FFAudioFileReader_getAudioFileFormatsFromURL(JNIEnv *env, jobject instance, jstring url, jbyteArray urlBytes)
Opens the given URL to determine its AudioFileFormat.
JNIEXPORT jobjectArray JNICALL Java_com_tagtraum_ffsampledsp_FFAudioFileReader_getAudioFileFormatsFromBuffer(JNIEnv *env, jobject instance, jobject byte_buffer)
Opens the byte buffer to determine its AudioFileFormat.
int ff_big_endian(enum AVCodecID codec_id)
Indicates whether the given id belongs to a big endian codec.
Definition FFUtils.c:1221
int ff_open_format_context(JNIEnv *env, AVFormatContext **format_context, const char *url, int io_buffer_size)
Opens the input file/url and allocates a AVFormatContext for it, but does not open the audio stream w...
Definition FFUtils.c:297
void throwUnsupportedAudioFileExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an UnsupportedAudioFileException.
Definition FFUtils.c:182
void throwIOExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an IOException.
Definition FFUtils.c:213
const uint32_t CODEC_TAG_DRMS
Definition FFUtils.c:27
Simple struct used for the read_callback of stream data.
int call_count
number of times the read_callback was called
jobject byte_buffer
byte buffer passed in (read only once)
JNIEnv * env
JNI environment.