int encode_gif(JNIEnv *env, jobjectArray bitmaps, const char *output_path, int intervalMs) {
AVFormatContext *avFormatContext = nullptr;
AVCodec *avCodec = nullptr;
AVCodecContext *avCodecContext = nullptr;
AVStream *avStream = nullptr;
SwsContext *sws_ctx = nullptr;
AVPacket *avPacket = nullptr;
AVFrame *frame = nullptr;
int bitmapCount;
int width;
int height;
jobject firstBitmap;
int result = 0;
int ret;
ret = avformat_alloc_output_context2(&avFormatContext, nullptr, "gif", nullptr);
if (ret < 0) {
result = -1;
goto free;
}
avCodec = avcodec_find_encoder(AV_CODEC_ID_GIF);
if (!avCodec) {
result = -2;
goto free;
}
avStream = avformat_new_stream(avFormatContext, avCodec);
if (!avStream) {
result = -3;
goto free;
}
avCodecContext = avcodec_alloc_context3(avCodec);
if (!avCodecContext) {
result = -4;
goto free;
}
firstBitmap = env->GetObjectArrayElement(bitmaps, 0);
AndroidBitmapInfo firstInfo;
AndroidBitmap_getInfo(env, firstBitmap, &firstInfo);
width = firstInfo.width;
height = firstInfo.height;
avCodecContext->pix_fmt = AV_PIX_FMT_RGB8;
avCodecContext->bit_rate = 400000;
avCodecContext->width = width;
avCodecContext->height = height;
avCodecContext->time_base = {1, 100};
ret = avcodec_parameters_from_context(avStream->codecpar, avCodecContext);
if (ret < 0) {
result = -5;
goto free;
}
ret = avcodec_open2(avCodecContext, avCodec, nullptr);
if (ret < 0) {
result = -6;
goto free;
}
sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGBA,
width, height, AV_PIX_FMT_RGB8,
SWS_BILINEAR, nullptr, nullptr, nullptr);
ret = avio_open(&avFormatContext->pb, output_path, AVIO_FLAG_WRITE);
if (ret < 0) {
result = -7;
goto free;
}
ret = avformat_write_header(avFormatContext, nullptr);
if (ret < 0) {
result = -8;
goto free;
}
bitmapCount = env->GetArrayLength(bitmaps);
avPacket = av_packet_alloc();
if (!avPacket) {
result = -9;
goto free;
}
frame = av_frame_alloc();
if (!frame) {
result = -10;
goto free;
}
frame->format = AV_PIX_FMT_RGB8;
frame->width = width;
frame->height = height;
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
result = -11;
goto free;
}
for (int i = 0; i < bitmapCount; i++) {
jobject thisBitmap = env->GetObjectArrayElement(bitmaps, i);
AndroidBitmapInfo thisInfo;
AndroidBitmap_getInfo(env, thisBitmap, &thisInfo);
void *pixels;
AndroidBitmap_lockPixels(env, thisBitmap, &pixels);
ret = av_frame_make_writable(frame);
if (ret < 0) {
result = -12;
goto free;
}
frame->pts = intervalMs / 10 * i;
uint8_t *src_data[1] = {(uint8_t *) pixels};
int src_linesize[1] = {static_cast<int>(thisInfo.stride)};
ret = sws_scale(sws_ctx, src_data, src_linesize, 0, thisInfo.height, frame->data,
frame->linesize);
if (ret < 0) {
result = -12;
goto free;
}
AndroidBitmap_unlockPixels(env, thisBitmap);
ret = avcodec_send_frame(avCodecContext, frame);
while (ret >= 0) {
av_packet_unref(avPacket);
ret = avcodec_receive_packet(avCodecContext, avPacket);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
av_interleaved_write_frame(avFormatContext, avPacket);
}
env->DeleteLocalRef(thisBitmap);
}
av_write_trailer(avFormatContext);
free:
if (avPacket != nullptr) {
av_packet_free(&avPacket);
}
if (frame != nullptr) {
av_frame_free(&frame);
}
if (avCodecContext != nullptr) {
avcodec_free_context(&avCodecContext);
}
if (avFormatContext != nullptr) {
avformat_free_context(avFormatContext);
}
if (sws_ctx != nullptr) {
sws_freeContext(sws_ctx);
}
return result;
}