Telegram-Android/TMessagesProj/jni/audio.c
2024-02-20 12:03:54 +04:00

995 lines
No EOL
29 KiB
C

#include <jni.h>
#include <ogg/ogg.h>
#include <stdio.h>
#include <opus.h>
#include <stdlib.h>
#include <time.h>
#include <opusfile.h>
#include <math.h>
#include "c_utils.h"
#include "libavformat/avformat.h"
typedef struct {
int version;
int channels; /* Number of channels: 1..255 */
int preskip;
ogg_uint32_t input_sample_rate;
int gain; /* in dB S7.8 should be zero whenever possible */
int channel_mapping;
/* The rest is only used if channel_mapping != 0 */
int nb_streams;
int nb_coupled;
unsigned char stream_map[255];
} OpusHeader;
typedef struct {
unsigned char *data;
int maxlen;
int pos;
} Packet;
typedef struct {
const unsigned char *data;
int maxlen;
int pos;
} ROPacket;
typedef struct {
void *readdata;
opus_int64 total_samples_per_channel;
int rawmode;
int channels;
long rate;
int gain;
int samplesize;
int endianness;
char *infilename;
int ignorelength;
int skip;
int extraout;
char *comments;
int comments_length;
int copy_comments;
} oe_enc_opt;
typedef struct {
ogg_int32_t _packetId;
opus_int64 bytes_written;
opus_int64 pages_out;
opus_int64 total_samples;
ogg_int64_t enc_granulepos;
int size_segments;
int last_segments;
ogg_int64_t last_granulepos;
opus_int32 min_bytes;
int max_frame_bytes;
} resume_data;
static int write_uint32(Packet *p, ogg_uint32_t val) {
if (p->pos > p->maxlen - 4) {
return 0;
}
p->data[p->pos ] = (val ) & 0xFF;
p->data[p->pos+1] = (val>> 8) & 0xFF;
p->data[p->pos+2] = (val>>16) & 0xFF;
p->data[p->pos+3] = (val>>24) & 0xFF;
p->pos += 4;
return 1;
}
static int write_uint16(Packet *p, ogg_uint16_t val) {
if (p->pos > p->maxlen-2) {
return 0;
}
p->data[p->pos ] = (val ) & 0xFF;
p->data[p->pos+1] = (val>> 8) & 0xFF;
p->pos += 2;
return 1;
}
static int write_chars(Packet *p, const unsigned char *str, int nb_chars)
{
int i;
if (p->pos>p->maxlen-nb_chars)
return 0;
for (i=0;i<nb_chars;i++)
p->data[p->pos++] = str[i];
return 1;
}
static int read_uint32(ROPacket *p, ogg_uint32_t *val)
{
if (p->pos>p->maxlen-4)
return 0;
*val = (ogg_uint32_t)p->data[p->pos ];
*val |= (ogg_uint32_t)p->data[p->pos+1]<< 8;
*val |= (ogg_uint32_t)p->data[p->pos+2]<<16;
*val |= (ogg_uint32_t)p->data[p->pos+3]<<24;
p->pos += 4;
return 1;
}
static int read_uint16(ROPacket *p, ogg_uint16_t *val)
{
if (p->pos>p->maxlen-2)
return 0;
*val = (ogg_uint16_t)p->data[p->pos ];
*val |= (ogg_uint16_t)p->data[p->pos+1]<<8;
p->pos += 2;
return 1;
}
static int read_chars(ROPacket *p, unsigned char *str, int nb_chars)
{
int i;
if (p->pos>p->maxlen-nb_chars)
return 0;
for (i=0;i<nb_chars;i++)
str[i] = p->data[p->pos++];
return 1;
}
int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len) {
int i;
Packet p;
unsigned char ch;
p.data = packet;
p.maxlen = len;
p.pos = 0;
if (len < 19) {
return 0;
}
if (!write_chars(&p, (const unsigned char *)"OpusHead", 8)) {
return 0;
}
ch = 1;
if (!write_chars(&p, &ch, 1)) {
return 0;
}
ch = h->channels;
if (!write_chars(&p, &ch, 1)) {
return 0;
}
if (!write_uint16(&p, h->preskip)) {
return 0;
}
if (!write_uint32(&p, h->input_sample_rate)) {
return 0;
}
if (!write_uint16(&p, h->gain)) {
return 0;
}
ch = h->channel_mapping;
if (!write_chars(&p, &ch, 1)) {
return 0;
}
if (h->channel_mapping != 0) {
ch = h->nb_streams;
if (!write_chars(&p, &ch, 1)) {
return 0;
}
ch = h->nb_coupled;
if (!write_chars(&p, &ch, 1)) {
return 0;
}
/* Multi-stream support */
for (i = 0; i < h->channels; i++) {
if (!write_chars(&p, &h->stream_map[i], 1)) {
return 0;
}
}
}
return p.pos;
}
#define writeint(buf, base, val) do { buf[base + 3] = ((val) >> 24) & 0xff; \
buf[base + 2]=((val) >> 16) & 0xff; \
buf[base + 1]=((val) >> 8) & 0xff; \
buf[base] = (val) & 0xff; \
} while(0)
static void comment_init(char **comments, int *length, const char *vendor_string) {
// The 'vendor' field should be the actual encoding library used
size_t vendor_length = strlen(vendor_string);
int user_comment_list_length = 0;
size_t len = 8 + 4 + vendor_length + 4;
char *p = (char *)malloc(len);
memcpy(p, "OpusTags", 8);
writeint(p, 8, vendor_length);
memcpy(p + 12, vendor_string, vendor_length);
writeint(p, 12 + vendor_length, user_comment_list_length);
*length = len;
*comments = p;
}
static void comment_pad(char **comments, int* length, size_t amount) {
if (amount > 0) {
char *p = *comments;
// Make sure there is at least amount worth of padding free, and round up to the maximum that fits in the current ogg segments
size_t newlen = (*length + amount + 255) / 255 * 255 - 1;
p = realloc(p, newlen);
for (int32_t i = *length; i < newlen; i++) {
p[i] = 0;
}
*comments = p;
*length = newlen;
}
}
static int writeOggPage(ogg_page *page, FILE *os) {
int written = fwrite(page->header, sizeof(unsigned char), (size_t) page->header_len, os);
written += fwrite(page->body, sizeof(unsigned char), (size_t) page->body_len, os);
return written;
}
const opus_int32 bitrate = OPUS_BITRATE_MAX;
const opus_int32 frame_size = 960;
const int with_cvbr = 1;
const int max_ogg_delay = 0;
const int comment_padding = 512;
opus_int32 rate = 48000;
opus_int32 coding_rate = 48000;
ogg_int32_t _packetId;
OpusEncoder *_encoder = 0;
uint8_t *_packet = 0;
ogg_stream_state os;
const char *_filePath;
FILE *_fileOs = 0;
oe_enc_opt inopt;
OpusHeader header;
opus_int32 min_bytes;
int max_frame_bytes;
ogg_packet op;
ogg_page og;
opus_int64 bytes_written;
opus_int64 pages_out;
opus_int64 total_samples;
ogg_int64_t enc_granulepos;
ogg_int64_t last_granulepos;
int size_segments;
int last_segments;
void cleanupRecorder() {
ogg_stream_flush(&os, &og);
if (_encoder) {
opus_encoder_destroy(_encoder);
_encoder = 0;
}
ogg_stream_clear(&os);
if (_packet) {
free(_packet);
_packet = 0;
}
if (_fileOs) {
fclose(_fileOs);
_fileOs = 0;
}
_packetId = -1;
bytes_written = 0;
pages_out = 0;
total_samples = 0;
enc_granulepos = 0;
size_segments = 0;
last_segments = 0;
last_granulepos = 0;
if (_filePath) {
free(_filePath);
_filePath = 0;
}
memset(&os, 0, sizeof(ogg_stream_state));
memset(&inopt, 0, sizeof(oe_enc_opt));
memset(&header, 0, sizeof(OpusHeader));
memset(&op, 0, sizeof(ogg_packet));
memset(&og, 0, sizeof(ogg_page));
}
int initRecorder(const char *path, opus_int32 sampleRate) {
cleanupRecorder();
coding_rate = sampleRate;
rate = sampleRate;
if (!path) {
LOGE("path is null");
return 0;
}
int length = strlen(path);
_filePath = (char*) malloc(length + 1);
strcpy(_filePath, path);
_fileOs = fopen(path, "w");
if (!_fileOs) {
LOGE("error cannot open file: %s", path);
return 0;
}
inopt.rate = rate;
inopt.gain = 0;
inopt.endianness = 0;
inopt.copy_comments = 0;
inopt.rawmode = 0;
inopt.ignorelength = 0;
inopt.samplesize = 16;
inopt.channels = 1;
inopt.skip = 0;
comment_init(&inopt.comments, &inopt.comments_length, opus_get_version_string());
if (rate != coding_rate) {
LOGE("Invalid rate");
return 0;
}
header.channels = 1;
header.channel_mapping = 0;
header.input_sample_rate = rate;
header.gain = inopt.gain;
header.nb_streams = 1;
int result = OPUS_OK;
_encoder = opus_encoder_create(coding_rate, 1, OPUS_APPLICATION_VOIP, &result);
if (result != OPUS_OK) {
LOGE("Error cannot create encoder: %s", opus_strerror(result));
return 0;
}
min_bytes = max_frame_bytes = (1275 * 3 + 7) * header.nb_streams;
_packet = malloc(max_frame_bytes);
result = opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate));
//result = opus_encoder_ctl(_encoder, OPUS_SET_COMPLEXITY(10));
if (result != OPUS_OK) {
LOGE("Error OPUS_SET_BITRATE returned: %s", opus_strerror(result));
return 0;
}
#ifdef OPUS_SET_LSB_DEPTH
result = opus_encoder_ctl(_encoder, OPUS_SET_LSB_DEPTH(MAX(8, MIN(24, inopt.samplesize))));
if (result != OPUS_OK) {
LOGE("Warning OPUS_SET_LSB_DEPTH returned: %s", opus_strerror(result));
}
#endif
opus_int32 lookahead;
result = opus_encoder_ctl(_encoder, OPUS_GET_LOOKAHEAD(&lookahead));
if (result != OPUS_OK) {
LOGE("Error OPUS_GET_LOOKAHEAD returned: %s", opus_strerror(result));
return 0;
}
inopt.skip += lookahead;
header.preskip = (int)(inopt.skip * (48000.0 / coding_rate));
inopt.extraout = (int)(header.preskip * (rate / 48000.0));
if (ogg_stream_init(&os, rand()) == -1) {
LOGE("Error: stream init failed");
return 0;
}
unsigned char header_data[100];
int packet_size = opus_header_to_packet(&header, header_data, 100);
op.packet = header_data;
op.bytes = packet_size;
op.b_o_s = 1;
op.e_o_s = 0;
op.granulepos = 0;
op.packetno = 0;
ogg_stream_packetin(&os, &op);
while ((result = ogg_stream_flush(&os, &og))) {
if (!result) {
break;
}
int pageBytesWritten = writeOggPage(&og, _fileOs);
if (pageBytesWritten != og.header_len + og.body_len) {
LOGE("Error: failed writing header to output stream");
return 0;
}
bytes_written += pageBytesWritten;
pages_out++;
}
comment_pad(&inopt.comments, &inopt.comments_length, comment_padding);
op.packet = (unsigned char *)inopt.comments;
op.bytes = inopt.comments_length;
op.b_o_s = 0;
op.e_o_s = 0;
op.granulepos = 0;
op.packetno = 1;
ogg_stream_packetin(&os, &op);
while ((result = ogg_stream_flush(&os, &og))) {
if (result == 0) {
break;
}
int writtenPageBytes = writeOggPage(&og, _fileOs);
if (writtenPageBytes != og.header_len + og.body_len) {
LOGE("Error: failed writing header to output stream");
return 0;
}
bytes_written += writtenPageBytes;
pages_out++;
}
free(inopt.comments);
return 1;
}
void saveResumeData() {
if (_filePath == NULL) {
return;
}
const char* ext = ".resume";
char* _resumeFilePath = (char*) malloc(strlen(_filePath) + strlen(ext) + 1);
strcpy(_resumeFilePath, _filePath);
strcat(_resumeFilePath, ext);
FILE* resumeFile = fopen(_resumeFilePath, "wb");
if (!resumeFile) {
LOGE("error cannot open resume file to write: %s", _resumeFilePath);
free(_resumeFilePath);
return;
}
resume_data data;
data._packetId = _packetId;
data.bytes_written = bytes_written;
data.pages_out = pages_out;
data.total_samples = total_samples;
data.enc_granulepos = enc_granulepos;
data.size_segments = size_segments;
data.last_segments = last_segments;
data.last_granulepos = last_granulepos;
data.min_bytes = min_bytes;
data.max_frame_bytes = max_frame_bytes;
if (fwrite(&data, sizeof(resume_data), 1, resumeFile) != 1) {
LOGE("error writing resume data to file: %s", _resumeFilePath);
}
fclose(resumeFile);
free(_resumeFilePath);
}
resume_data readResumeData(const char* filePath) {
const char* ext = ".resume";
char* _resumeFilePath = (char*) malloc(strlen(filePath) + strlen(ext) + 1);
strcpy(_resumeFilePath, filePath);
strcat(_resumeFilePath, ext);
resume_data data;
FILE* resumeFile = fopen(_resumeFilePath, "rb");
if (!resumeFile) {
LOGE("error cannot open resume file to read: %s", _resumeFilePath);
memset(&data, 0, sizeof(resume_data));
return data;
}
if (fread(&data, sizeof(resume_data), 1, resumeFile) != 1) {
LOGE("error cannot read resume file: %s", _resumeFilePath);
memset(&data, 0, sizeof(resume_data));
}
fclose(resumeFile);
free(_resumeFilePath);
return data;
}
int resumeRecorder(const char *path, opus_int32 sampleRate) {
cleanupRecorder();
coding_rate = sampleRate;
rate = sampleRate;
if (!path) {
LOGE("path is null");
return 0;
}
int length = strlen(path);
_filePath = (char*) malloc(length + 1);
strcpy(_filePath, path);
resume_data resumeData = readResumeData(path);
_packetId = resumeData._packetId;
bytes_written = resumeData.bytes_written;
pages_out = resumeData.pages_out;
total_samples = resumeData.total_samples;
enc_granulepos = resumeData.enc_granulepos;
size_segments = resumeData.size_segments;
last_segments = resumeData.last_segments;
last_granulepos = resumeData.last_granulepos;
min_bytes = resumeData.min_bytes;
max_frame_bytes = resumeData.max_frame_bytes;
_fileOs = fopen(path, "a");
if (!_fileOs) {
LOGE("error cannot open resume file: %s", path);
return 0;
}
int result = OPUS_OK;
_encoder = opus_encoder_create(coding_rate, 1, OPUS_APPLICATION_VOIP, &result);
if (result != OPUS_OK) {
LOGE("Error cannot create encoder: %s", opus_strerror(result));
return 0;
}
_packet = malloc(max_frame_bytes);
result = opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate));
//result = opus_encoder_ctl(_encoder, OPUS_SET_COMPLEXITY(10));
if (result != OPUS_OK) {
LOGE("Error OPUS_SET_BITRATE returned: %s", opus_strerror(result));
return 0;
}
#ifdef OPUS_SET_LSB_DEPTH
result = opus_encoder_ctl(_encoder, OPUS_SET_LSB_DEPTH(MAX(8, MIN(24, 16))));
if (result != OPUS_OK) {
LOGE("Warning OPUS_SET_LSB_DEPTH returned: %s", opus_strerror(result));
}
#endif
if (ogg_stream_init(&os, rand()) == -1) {
LOGE("Error: stream init failed");
return 0;
}
return 1;
}
int writeFrame(uint8_t *framePcmBytes, uint32_t frameByteCount) {
size_t cur_frame_size = frame_size;
_packetId++;
opus_int32 nb_samples = frameByteCount / 2;
total_samples += nb_samples;
if (nb_samples < frame_size) {
op.e_o_s = 1;
} else {
op.e_o_s = 0;
}
int nbBytes = 0;
if (nb_samples != 0) {
uint8_t *paddedFrameBytes = framePcmBytes;
int freePaddedFrameBytes = 0;
if (nb_samples < cur_frame_size) {
paddedFrameBytes = malloc(cur_frame_size * 2);
freePaddedFrameBytes = 1;
memcpy(paddedFrameBytes, framePcmBytes, frameByteCount);
memset(paddedFrameBytes + nb_samples * 2, 0, cur_frame_size * 2 - nb_samples * 2);
}
nbBytes = opus_encode(_encoder, (opus_int16 *)paddedFrameBytes, cur_frame_size, _packet, max_frame_bytes / 10);
if (freePaddedFrameBytes) {
free(paddedFrameBytes);
}
if (nbBytes < 0) {
LOGE("Encoding failed: %s. Aborting.", opus_strerror(nbBytes));
return 0;
}
enc_granulepos += cur_frame_size * 48000 / coding_rate;
size_segments = (nbBytes + 255) / 255;
min_bytes = MIN(nbBytes, min_bytes);
}
while ((((size_segments <= 255) && (last_segments + size_segments > 255)) || (enc_granulepos - last_granulepos > max_ogg_delay)) && ogg_stream_flush_fill(&os, &og, 255 * 255)) {
if (ogg_page_packets(&og) != 0) {
last_granulepos = ogg_page_granulepos(&og);
}
last_segments -= og.header[26];
int writtenPageBytes = writeOggPage(&og, _fileOs);
if (writtenPageBytes != og.header_len + og.body_len) {
LOGE("Error: failed writing data to output stream");
return 0;
}
bytes_written += writtenPageBytes;
pages_out++;
}
op.packet = _packet;
op.bytes = nbBytes;
op.b_o_s = 0;
op.granulepos = enc_granulepos;
if (op.e_o_s) {
op.granulepos = ((total_samples * 48000 + rate - 1) / rate) + header.preskip;
}
op.packetno = 2 + _packetId;
ogg_stream_packetin(&os, &op);
last_segments += size_segments;
while ((op.e_o_s || (enc_granulepos + (frame_size * 48000 / coding_rate) - last_granulepos > max_ogg_delay) || (last_segments >= 255)) ? ogg_stream_flush_fill(&os, &og, 255 * 255) : ogg_stream_pageout_fill(&os, &og, 255 * 255)) {
if (ogg_page_packets(&og) != 0) {
last_granulepos = ogg_page_granulepos(&og);
}
last_segments -= og.header[26];
int writtenPageBytes = writeOggPage(&og, _fileOs);
if (writtenPageBytes != og.header_len + og.body_len) {
LOGE("Error: failed writing data to output stream");
return 0;
}
bytes_written += writtenPageBytes;
pages_out++;
}
return 1;
}
JNIEXPORT jint Java_org_telegram_messenger_MediaController_startRecord(JNIEnv *env, jclass class, jstring path, jint sampleRate) {
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
int32_t result = initRecorder(pathStr, sampleRate);
if (pathStr != 0) {
(*env)->ReleaseStringUTFChars(env, path, pathStr);
}
return result;
}
JNIEXPORT jint Java_org_telegram_messenger_MediaController_resumeRecord(JNIEnv *env, jclass class, jstring path, jint sampleRate) {
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
int32_t result = resumeRecorder(pathStr, sampleRate);
if (pathStr != 0) {
(*env)->ReleaseStringUTFChars(env, path, pathStr);
}
return result;
}
JNIEXPORT jint Java_org_telegram_messenger_MediaController_writeFrame(JNIEnv *env, jclass class, jobject frame, jint len) {
jbyte *frameBytes = (*env)->GetDirectBufferAddress(env, frame);
return writeFrame((uint8_t *) frameBytes, (uint32_t) len);
}
JNIEXPORT void Java_org_telegram_messenger_MediaController_stopRecord(JNIEnv *env, jclass class, jboolean allowResuming) {
if (allowResuming && _filePath != NULL) {
saveResumeData();
}
cleanupRecorder();
}
JNIEXPORT jint Java_org_telegram_messenger_MediaController_isOpusFile(JNIEnv *env, jclass class, jstring path) {
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
int32_t result = 0;
int32_t error = OPUS_OK;
OggOpusFile *file = op_test_file(pathStr, &error);
if (file != NULL) {
error = op_test_open(file);
op_free(file);
result = error == OPUS_OK;
}
if (pathStr != 0) {
(*env)->ReleaseStringUTFChars(env, path, pathStr);
}
return result;
}
static inline void set_bits(uint8_t *bytes, int32_t bitOffset, int32_t value) {
bytes += bitOffset / 8;
bitOffset %= 8;
*((int32_t *) bytes) |= (value << bitOffset);
}
JNIEXPORT jbyteArray Java_org_telegram_messenger_MediaController_getWaveform2(JNIEnv *env, jclass class, jshortArray array, jint length) {
jshort *sampleBuffer = (*env)->GetShortArrayElements(env, array, 0);
const int32_t resultSamples = 100;
uint16_t *samples = malloc(100 * 2);
uint64_t sampleIndex = 0;
uint16_t peakSample = 0;
int32_t sampleRate = (int32_t) MAX(1, length / resultSamples);
int32_t index = 0;
for (int32_t i = 0; i < length; i++) {
uint16_t sample = (uint16_t) abs(sampleBuffer[i]);
if (sample > peakSample) {
peakSample = sample;
}
if (sampleIndex++ % sampleRate == 0) {
if (index < resultSamples) {
samples[index++] = peakSample;
}
peakSample = 0;
}
}
int64_t sumSamples = 0;
for (int32_t i = 0; i < resultSamples; i++) {
sumSamples += samples[i];
}
uint16_t peak = (uint16_t) (sumSamples * 1.8f / resultSamples);
if (peak < 2500) {
peak = 2500;
}
for (int32_t i = 0; i < resultSamples; i++) {
uint16_t sample = (uint16_t) ((int64_t) samples[i]);
if (sample > peak) {
samples[i] = peak;
}
}
(*env)->ReleaseShortArrayElements(env, array, sampleBuffer, 0);
uint32_t bitstreamLength = resultSamples * 5 / 8 + 1;
jbyteArray *result = (*env)->NewByteArray(env, bitstreamLength);
if (result) {
uint8_t *bytes = malloc(bitstreamLength + 4);
memset(bytes, 0, bitstreamLength + 4);
for (int32_t i = 0; i < resultSamples; i++) {
int32_t value = MIN(31, abs((int32_t) samples[i]) * 31 / peak);
set_bits(bytes, i * 5, value & 31);
}
(*env)->SetByteArrayRegion(env, result, 0, bitstreamLength, (jbyte *) bytes);
}
free(samples);
return result;
}
int16_t *sampleBuffer = NULL;
JNIEXPORT jbyteArray Java_org_telegram_messenger_MediaController_getWaveform(JNIEnv *env, jclass class, jstring path) {
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
jbyteArray result = 0;
int error = OPUS_OK;
OggOpusFile *opusFile = op_open_file(pathStr, &error);
if (opusFile != NULL && error == OPUS_OK) {
int64_t totalSamples = op_pcm_total(opusFile, -1);
const uint32_t resultSamples = 100;
int32_t sampleRate = MAX(1, (int32_t) (totalSamples / resultSamples));
uint16_t *samples = malloc(100 * 2);
size_t bufferSize = 1024 * 128;
if (sampleBuffer == NULL) {
sampleBuffer = malloc(bufferSize);
}
uint64_t sampleIndex = 0;
uint16_t peakSample = 0;
int32_t index = 0;
while (1) {
int readSamples = op_read(opusFile, sampleBuffer, bufferSize / 2, NULL);
for (int32_t i = 0; i < readSamples; i++) {
uint16_t sample = (uint16_t) abs(sampleBuffer[i]);
if (sample > peakSample) {
peakSample = sample;
}
if (sampleIndex++ % sampleRate == 0) {
if (index < resultSamples) {
samples[index++] = peakSample;
}
peakSample = 0;
}
}
if (readSamples == 0) {
break;
}
}
int64_t sumSamples = 0;
for (int32_t i = 0; i < resultSamples; i++) {
sumSamples += samples[i];
}
uint16_t peak = (uint16_t) (sumSamples * 1.8f / resultSamples);
if (peak < 2500) {
peak = 2500;
}
for (int32_t i = 0; i < resultSamples; i++) {
uint16_t sample = (uint16_t) ((int64_t) samples[i]);
if (sample > peak) {
samples[i] = peak;
}
}
//free(sampleBuffer);
op_free(opusFile);
uint32_t bitstreamLength = (resultSamples * 5) / 8 + 1;
result = (*env)->NewByteArray(env, bitstreamLength);
if (result) {
uint8_t *bytes = malloc(bitstreamLength + 4);
memset(bytes, 0, bitstreamLength + 4);
for (int32_t i = 0; i < resultSamples; i++) {
int32_t value = MIN(31, abs((int32_t) samples[i]) * 31 / peak);
set_bits(bytes, i * 5, value & 31);
}
(*env)->SetByteArrayRegion(env, result, 0, bitstreamLength, (jbyte *) bytes);
}
free(samples);
}
if (pathStr != 0) {
(*env)->ReleaseStringUTFChars(env, path, pathStr);
}
return result;
}
JNIEXPORT void JNICALL Java_org_telegram_ui_Stories_recorder_FfmpegAudioWaveformLoader_init(JNIEnv *env, jobject obj, jstring pathJStr, jint count) {
const char *path = (*env)->GetStringUTFChars(env, pathJStr, 0);
// Initialize FFmpeg components
av_register_all();
AVFormatContext *formatContext = avformat_alloc_context();
if (!formatContext) {
// Handle error
return;
}
int res;
if ((res = avformat_open_input(&formatContext, path, NULL, NULL)) != 0) {
LOGD("avformat_open_input error %s", av_err2str(res));
// Handle error
avformat_free_context(formatContext);
return;
}
if (avformat_find_stream_info(formatContext, NULL) < 0) {
// Handle error
avformat_close_input(&formatContext);
return;
}
AVCodec *codec = NULL;
int audioStreamIndex = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
if (audioStreamIndex < 0) {
LOGD("av_find_best_stream error %s", av_err2str(audioStreamIndex));
// Handle error
avformat_close_input(&formatContext);
return;
}
AVCodecContext *codecContext = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecContext, formatContext->streams[audioStreamIndex]->codecpar);
int64_t duration_in_microseconds = formatContext->duration;
double duration_in_seconds = (double)duration_in_microseconds / AV_TIME_BASE;
if (avcodec_open2(codecContext, codec, NULL) < 0) {
// Handle error
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return;
}
// Obtain the class and method to callback
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "receiveChunk", "([SI)V");
AVFrame *frame = av_frame_alloc();
AVPacket packet;
int sampleRate = codecContext->sample_rate; // Sample rate from FFmpeg's codec context
int skip = 4;
int barWidth = (int) round((double) duration_in_seconds * sampleRate / count / (1 + skip)); // Assuming you have 'duration' and 'count' defined somewhere
short peak = 0;
int currentCount = 0;
int index = 0;
int chunkIndex = 0;
short waveformChunkData[32]; // Allocate the chunk array
while (av_read_frame(formatContext, &packet) >= 0) {
if (packet.stream_index == audioStreamIndex) {
// Decode the audio packet
int response = avcodec_send_packet(codecContext, &packet);
while (response >= 0) {
response = avcodec_receive_frame(codecContext, frame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
} else if (response < 0) {
// Handle error
break;
}
int16_t* samples = (int16_t*) frame->data[0];
for (int i = 0; i < frame->nb_samples; i++) {
short value = samples[i]; // Read the 16-bit PCM sample
if (currentCount >= barWidth) {
waveformChunkData[index - chunkIndex] = peak;
index++;
if (index - chunkIndex >= sizeof(waveformChunkData) / sizeof(short) || index >= count) {
jshortArray waveformData = (*env)->NewShortArray(env, sizeof(waveformChunkData) / sizeof(short));
(*env)->SetShortArrayRegion(env, waveformData, 0, sizeof(waveformChunkData) / sizeof(short), waveformChunkData);
(*env)->CallVoidMethod(env, obj, mid, waveformData, sizeof(waveformChunkData) / sizeof(short));
// Reset the chunk data
memset(waveformChunkData, 0, sizeof(waveformChunkData));
chunkIndex = index;
// Delete local reference to avoid memory leak
(*env)->DeleteLocalRef(env, waveformData);
}
peak = 0;
currentCount = 0;
if (index >= count) {
break;
}
}
if (peak < value) {
peak = value;
}
currentCount++;
// Skip logic
i += skip;
if (i >= frame->nb_samples) {
break;
}
}
}
}
av_packet_unref(&packet);
if (index >= count) {
break;
}
// Check for stopping flag
jfieldID fid = (*env)->GetFieldID(env, cls, "running", "Z");
jboolean running = (*env)->GetBooleanField(env, obj, fid);
if (running == JNI_FALSE) {
break;
}
}
av_frame_free(&frame);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
(*env)->ReleaseStringUTFChars(env, pathJStr, path);
}