#include <pthread.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#define DELETE(ptr) if (ptr != NULL) { free(ptr); ptr = NULL; }
pthread_mutex_t rc_mutex;
int rc;
enum EarwaxError {
IO_ERROR = 100,
AUDIO_STREAM_NOT_FOUND,
DECODER_NOT_FOUND,
UNABLE_TO_OPEN_DECODER,
};
typedef struct {
int64_t num;
int64_t den;
} EarwaxRational;
typedef struct {
int bitrate;
int sample_rate;
int64_t start_time;
int64_t duration;
EarwaxRational time_base;
} EarwaxInfo;
typedef struct {
AVFormatContext* format_ctx;
AVCodecContext* codec_ctx;
int stream_index;
SwrContext* swr;
AVPacket packet;
AVFrame* frame;
uint8_t* buffer;
EarwaxInfo info;
} EarwaxContext;
typedef struct {
char* data;
size_t size;
int64_t time;
} EarwaxChunk;
void earwax_init();
void earwax_shutdown();
int earwax_new(EarwaxContext** ctx_ptr, const char* url);
void earwax_drop(EarwaxContext** ctx);
void earwax_get_info(const EarwaxContext* ctx, EarwaxInfo* info);
int earwax_spit(EarwaxContext* ctx, EarwaxChunk* chunk);
int earwax_seek(EarwaxContext* ctx, int64_t pts);
int next_chunk(EarwaxContext* ctx, EarwaxChunk* chunk);
void earwax_init() {
pthread_mutex_lock(&rc_mutex);
if (rc <= 0) {
av_register_all();
avformat_network_init();
}
pthread_mutex_unlock(&rc_mutex);
}
void earwax_shutdown() {
pthread_mutex_lock(&rc_mutex);
if (rc <= 0) {
avformat_network_deinit();
}
pthread_mutex_unlock(&rc_mutex);
}
int earwax_new(EarwaxContext** ctx_ptr, const char* url) {
pthread_mutex_lock(&rc_mutex);
rc++;
*ctx_ptr = calloc(1, sizeof(EarwaxContext));
EarwaxContext* ctx = *ctx_ptr;
int ret_code = 0;
if (avformat_open_input(&ctx->format_ctx, url, NULL, NULL) != 0) {
ret_code = IO_ERROR;
goto FAIL;
}
if(avformat_find_stream_info(ctx->format_ctx, NULL) < 0) {
ret_code = IO_ERROR;
goto FAIL;
}
ctx->stream_index = -1;
ctx->codec_ctx = avcodec_alloc_context3(NULL);
int i;
for (i = 0; i < ctx->format_ctx->nb_streams; ++i) {
AVCodecContext* codec_ctx = ctx->format_ctx->streams[i]->codec;
if (codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
avcodec_copy_context(ctx->codec_ctx, codec_ctx);
ctx->stream_index = i;
}
}
if (ctx->stream_index < 0) {
ret_code = AUDIO_STREAM_NOT_FOUND;
goto FAIL;
}
AVCodec* codec;
codec = avcodec_find_decoder(ctx->codec_ctx->codec_id);
if (codec == NULL) {
ret_code = DECODER_NOT_FOUND;
goto FAIL;
}
if (avcodec_open2(ctx->codec_ctx, codec, NULL) < 0) {
ret_code = UNABLE_TO_OPEN_DECODER;
goto FAIL;
}
ctx->swr = swr_alloc();
av_opt_set_int(ctx->swr, "in_channel_layout", ctx->codec_ctx->channel_layout, 0);
av_opt_set_int(ctx->swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(ctx->swr, "in_sample_rate", ctx->codec_ctx->sample_rate, 0);
av_opt_set_int(ctx->swr, "out_sample_rate", ctx->codec_ctx->sample_rate, 0);
av_opt_set_sample_fmt(ctx->swr, "in_sample_fmt", ctx->codec_ctx->sample_fmt, 0);
av_opt_set_sample_fmt(ctx->swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
swr_init(ctx->swr);
ctx->frame = av_frame_alloc();
ctx->buffer = malloc(
(ctx->codec_ctx->frame_size + AV_INPUT_BUFFER_PADDING_SIZE)
* ctx->codec_ctx->channels
* sizeof(uint16_t)
);
earwax_get_info(ctx, &ctx->info);
goto SUCCESS;
FAIL:
pthread_mutex_unlock(&rc_mutex);
earwax_drop(ctx_ptr);
return ret_code;
SUCCESS:
pthread_mutex_unlock(&rc_mutex);
return 0;
}
void earwax_drop(EarwaxContext** ctx_ptr) {
pthread_mutex_lock(&rc_mutex);
if (*ctx_ptr != NULL) {
EarwaxContext* ctx = *ctx_ptr;
DELETE(ctx->buffer);
av_frame_unref(ctx->frame);
av_frame_free(&ctx->frame);
av_free_packet(&ctx->packet);
swr_free(&ctx->swr);
avcodec_free_context(&ctx->codec_ctx);
avformat_close_input(&ctx->format_ctx);
avformat_free_context(ctx->format_ctx);
DELETE(*ctx_ptr);
rc--;
}
pthread_mutex_unlock(&rc_mutex);
}
void earwax_get_info(const EarwaxContext* ctx, EarwaxInfo* info) {
memset(info, 0, sizeof(EarwaxInfo));
if (ctx != NULL) {
AVStream* stream = ctx->format_ctx->streams[ctx->stream_index];
info->bitrate = ctx->codec_ctx->bit_rate;
info->sample_rate = ctx->codec_ctx->sample_rate;
info->start_time = stream->start_time;
info->duration = stream->duration;
info->time_base.num = stream->time_base.num;
info->time_base.den = stream->time_base.den;
}
}
int earwax_spit(EarwaxContext* ctx, EarwaxChunk* chunk) {
if (ctx->packet.size > 0) {
int got_frame = 0;
int bytes = avcodec_decode_audio4(ctx->codec_ctx, ctx->frame, &got_frame, &(ctx->packet));
if (bytes > 0) {
ctx->packet.size -= bytes;
ctx->packet.data += bytes;
}
if (got_frame) {
return next_chunk(ctx, chunk);
}
else if (ctx->packet.size > 0 && bytes > 0) {
return earwax_spit(ctx, chunk);
}
else {
ctx->packet.size = 0;
ctx->packet.data = NULL;
return earwax_spit(ctx, chunk);
}
}
else {
av_frame_unref(ctx->frame);
av_free_packet(&ctx->packet);
if (av_read_frame(ctx->format_ctx, &(ctx->packet)) == 0) {
if (ctx->packet.stream_index == ctx->stream_index) {
return earwax_spit(ctx, chunk);
}
else {
ctx->packet.size = 0;
ctx->packet.data = NULL;
return earwax_spit(ctx, chunk);
}
}
}
return 0;
}
int earwax_seek(EarwaxContext* ctx, int64_t pts) {
int64_t start_time = ctx->info.start_time;
int64_t duration = ctx->info.duration;
if (pts < start_time) pts = start_time;
if (pts > duration) pts = duration;
return av_seek_frame(ctx->format_ctx, ctx->stream_index, pts, AVSEEK_FLAG_BACKWARD);
}
void earwax_log_level(int level) {
av_log_set_level(level);
}
int next_chunk(EarwaxContext* ctx, EarwaxChunk* chunk) {
swr_convert(
ctx->swr,
&ctx->buffer, ctx->frame->nb_samples,
(const uint8_t**) ctx->frame->extended_data, ctx->frame->nb_samples
);
chunk->data = ctx->buffer;
chunk->size = ctx->frame->nb_samples * ctx->frame->channels * sizeof(uint16_t);
chunk->time = ctx->frame->pkt_pts;
return chunk->size;
}