//
// Created by 阳坤 on 2022/3/1.
//

#include "av_decode.h"


static int
openFFmpegDecoderContext(AVFormatContext *fmtCtx, enum AVMediaType type, int *streamIdx, AVCodecContext **decCtx,
                         enum AVCodecID codecId) {
    if (!fmtCtx)return -1;
    int ret = -1;
    int streamIndex = *streamIdx;
    AVCodecContext *ctx = NULL;
    AVCodecParameters *parameters = fmtCtx->streams[streamIndex]->codecpar;;
    AVCodec *codec = avcodec_find_decoder(codecId);
    if (codec == NULL) {
        if (codecId == AV_CODEC_ID_PNG)
            LOGE("openFFmpegDecoderContext avcodec_find_decoder png not found. \n");
        LOGE("openFFmpegDecoderContext avcodec_find_decoder=%d not found. \n", codecId);
        return -1;
    }
    ctx = avcodec_alloc_context3(codec);

    if (!ctx || !parameters || !codec) {
        LOGE("openFFmpegDecoderContext init error %d ctx==null=%d  parameters==null=%d  codec==null=%d \n", codecId,
             ctx == NULL, parameters == NULL, codec == NULL);
        return -1;
    }


    if ((ret = avcodec_parameters_to_context(ctx, parameters)) != 0) {
        Log("Failed to copy %s codec parameters to decoder context.", av_get_media_type_string(type));
        return -1;
    }

    switch (type) {
        case AVMEDIA_TYPE_VIDEO:
            ctx->framerate = av_guess_frame_rate(fmtCtx, fmtCtx->streams[streamIndex],
                                                 NULL);
            break;
        case AVMEDIA_TYPE_AUDIO:
            break;
    }
    ctx->thread_count = 3;
    if ((ret = avcodec_open2(ctx, codec, NULL)) != 0) {
        char buf[512];
        av_strerror(ret, buf, 512);
        LOGE("Failed to open %s codec. error=%s \n", av_get_media_type_string(type), buf);
        return -1;
    }
    *decCtx = ctx;
    avcodec_flush_buffers(ctx);
    ret = 0;
    return ret;
}


static int av_decode(struct DecoderContext *pContext, AVPacket *pPacket, enum AVMediaType type) {
    AVCodecContext *ctx = NULL;
    switch (type) {
        case AVMEDIA_TYPE_VIDEO:
            ctx = pContext->videoCodecContext;
            break;
        case AVMEDIA_TYPE_AUDIO:
            ctx = pContext->audioCodecContext;
            break;
    }
    if (!ctx)return -1;
    int ret = 0;
    if (pPacket)
        ret = avcodec_send_packet(ctx, pPacket);
    else ret = avcodec_send_packet(ctx, NULL);
    if (ret != 0) {
        return -1;
    }
    while (1) {
        AVFrame *out_frame = av_frame_alloc();
        ret = avcodec_receive_frame(ctx, out_frame);
        if (ret != 0) {
            av_frame_free(&out_frame);
            break;
        }

        int64_t pts = -1;
        if (pPacket)
            pts = out_frame->pts * (1000 *
                                    (av_q2d(pContext->avformatContext->streams[(pPacket)->stream_index]->time_base)));
        else
            pts = out_frame->pts * (1000 *
                                    (av_q2d(pContext->avformatContext->streams[pContext->st_index[type]]->time_base)));


//        int64_t offset = 0;
//        if (pContext->offset != AV_NOPTS_VALUE) {
//            offset += pContext->offset / 1000;
//            pts -= offset;
//        }

        if (pContext->end_time > 0 && pts > pContext->end_time && type == AVMEDIA_TYPE_VIDEO) {
            av_frame_free(&out_frame);
            return -4;
        }
        if (pContext->end_time > 0 && pts > pContext->end_time && type == AVMEDIA_TYPE_AUDIO) {
            av_frame_free(&out_frame);
            return 0;
        }

//        LOGE("decode is video=%d pts=%lld \n",pPacket->stream_index == pContext->st_index[AVMEDIA_TYPE_VIDEO],pts);
        if (pts < pContext->start_time && pContext->start_time >= 0 && pContext->force_Iframe != 1) {
            av_frame_free(&out_frame);
            continue;
        }

        double def_frame_delay =
                (double) (1000.0 / pContext->fps);
        //如果持续时间很短,那么就用每帧固定延迟时间
        if (out_frame->pkt_duration < 10) {
            pContext->decode_frame_ms += (long) def_frame_delay;
        } else {//使用更加精准的帧延迟时间
            //根据每帧的延迟时间来相加，更加精准,有些 pkt_duration 持续时长是 0
            pContext->decode_frame_ms += (int64_t) (out_frame->pkt_duration *
                                                    ((AV_TIME_BASE *
                                                      (av_q2d(pContext->avformatContext->streams[pContext->st_index[AVMEDIA_TYPE_VIDEO]]->time_base))))) /
                                         1000;
        }

        out_frame->pts = pts;
        out_frame->pkt_dts = pts;
        switch (type) {
            case AVMEDIA_TYPE_VIDEO:
                pContext->video_queue->PushBack(out_frame);
                break;
            case AVMEDIA_TYPE_AUDIO:
                pContext->audio_queue->PushBack(out_frame);
                break;
        }
    }
    return ret;
}

/**
 * 初始化解码器
 * @param url 
 * @param outVideoFormat 
 * @param force_Iframe 
 * @param end_time
 * @return 
 */
long initDecoder(const char *url, int force_Iframe, DisableMediaType disableMediaType) {
    struct DecoderContext *dctx = (struct DecoderContext *) malloc(sizeof(struct DecoderContext));
    if (!dctx) {
        LOGE("DecoderContext create fail.");
        return -1;
    }
    memset(dctx, 0, sizeof(struct DecoderContext));
    dctx->url = strdup(url);
    dctx->force_Iframe = force_Iframe;
    dctx->ffthread_count = 0;
    dctx->start_time = 0;
    dctx->end_time = -1;
    dctx->disableType = disableMediaType;
    char err_info[512] = {0};
    avformat_network_init();
    int ret = avformat_open_input(&(dctx->avformatContext), dctx->url, NULL, NULL);

    if (ret != 0) {
        av_strerror(ret, err_info, 512);
        Log("avformat_open_input failed %d %s. path=%s \n", ret, err_info, dctx->url);
        close_decoder((long) dctx);
        return ret;
    }

    ret = avformat_find_stream_info(dctx->avformatContext, 0);
    if (ret < 0) {
        av_strerror(ret, err_info, 512);
        Log("Failed to retrieve input stream information msg=%s\n", err_info);
        close_decoder((long) dctx);
        return ret;
    }

    dctx->st_index[AVMEDIA_TYPE_VIDEO] =
            av_find_best_stream(dctx->avformatContext, AVMEDIA_TYPE_VIDEO,
                                dctx->st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
    if (dctx->st_index[AVMEDIA_TYPE_VIDEO] < 0) {
        for (int i = 0; i < dctx->avformatContext->nb_streams; ++i) {
            if (dctx->avformatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
                dctx->st_index[AVMEDIA_TYPE_VIDEO] = i;

        }
    }
    dctx->st_index[AVMEDIA_TYPE_AUDIO] = av_find_best_stream(dctx->avformatContext,
                                                             AVMEDIA_TYPE_AUDIO,
                                                             -1,
                                                             -1,
                                                             NULL, 0);

    if (dctx->st_index[AVMEDIA_TYPE_AUDIO] < 0) {
        for (int i = 0; i < dctx->avformatContext->nb_streams; ++i) {
            if (dctx->avformatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
                dctx->st_index[AVMEDIA_TYPE_AUDIO] = i;

        }
    }
    if (dctx->st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
        AVStream *as = dctx->avformatContext->streams[dctx->st_index[AVMEDIA_TYPE_VIDEO]];
        dctx->v_codec_id = as->codecpar->codec_id;
        dctx->vformat = as->codecpar->format;
        dctx->width = as->codecpar->width;
        dctx->height = as->codecpar->height;
        dctx->v_bit_rate = as->codecpar->bit_rate;
        if (dctx->v_bit_rate <= 0 && dctx->avformatContext->bit_rate > 0) {
            dctx->v_bit_rate = dctx->avformatContext->bit_rate;
        } else if (dctx->v_bit_rate <= 0) {
            dctx->v_bit_rate = dctx->width * dctx->height * 3;
        }
        dctx->fps = av_q2d(as->avg_frame_rate);//拿到视频码率
        dctx->rotate = getVideoRotate(as->metadata);

        LOGE("找到视频流 width=%d height=%d bit_rate=%d fps=%d rotate=%d\n", dctx->width,
             dctx->height, dctx->v_bit_rate, dctx->fps,
             dctx->rotate);

    }
    if (dctx->st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
        AVStream *as = dctx->avformatContext->streams[dctx->st_index[AVMEDIA_TYPE_AUDIO]];
        dctx->a_codec_id = as->codecpar->codec_id;
        dctx->sampleRate = as->codecpar->sample_rate;
        dctx->channels = as->codecpar->channels;
        dctx->a_bit_rate = as->codecpar->bit_rate;
        dctx->aformat = as->codecpar->format;
        dctx->channels_layout = as->codecpar->channel_layout;
        LOGE("找到音频流 sampleRate=%d channels=%d bit_rate=%d aformat=%d channels_layout=%d\n",
             dctx->sampleRate, dctx->channels, dctx->a_bit_rate,
             dctx->aformat, dctx->channels_layout);


    }
    dctx->totalMs = dctx->avformatContext->duration / (AV_TIME_BASE / 1000);
    if (dctx->start_time > dctx->totalMs) {
        LOGE("媒体总时长 totalMs=%lld start_time=%lld \n", dctx->totalMs, dctx->start_time);
        return -1;
    }

    LOGE("媒体总时长 totalMs=%lld \n", dctx->totalMs);
    if (dctx->avformatContext->start_time != AV_NOPTS_VALUE)
        dctx->offset = dctx->avformatContext->start_time;


    if (dctx->st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
        ret = openFFmpegDecoderContext(dctx->avformatContext, AVMEDIA_TYPE_AUDIO,
                                       &dctx->st_index[AVMEDIA_TYPE_AUDIO],
                                       &dctx->audioCodecContext, dctx->a_codec_id);

        if (ret != 0) {
            LOGE("audio openFFmpegDecoderContext error！");
            close_decoder((long) dctx);
            return ret;
        }


    }

    if (dctx->st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
        ret = openFFmpegDecoderContext(dctx->avformatContext, AVMEDIA_TYPE_VIDEO,
                                       &dctx->st_index[AVMEDIA_TYPE_VIDEO],
                                       &dctx->videoCodecContext, dctx->v_codec_id);
        if (ret != 0) {
            LOGE("video openFFmpegDecoderContext error！");
            close_decoder((long) dctx);
            return ret;
        }
//        dctx->audio_queue = new BlockQueue<AVFrame *>(dctx->fps);
        dctx->video_queue = new BlockQueue<AVFrame *>(dctx->fps);
    }

    seekToMs(dctx, dctx->offset);
    return ret == 0 ? (long) dctx : ret;
}

static int is_realtime(AVFormatContext *s) {
    if (!strcmp(s->iformat->name, "rtp")
        || !strcmp(s->iformat->name, "rtsp")
        || !strcmp(s->iformat->name, "sdp")
        || !strcmp(s->iformat->name, "hls")
            )
        return 1;

//    if (s->pb && (!strncmp(s->url, "rtp:", 4)
//                  || !strncmp(s->url, "udp:", 4)
//    )
//            )
//        return 1;
    return 0;
}

void seekToMs(DecoderContext *dctx, int64_t offset) {
    int64_t timestamp = dctx->start_time * 1000;
    if (offset != AV_NOPTS_VALUE)
        timestamp += offset;
    if (timestamp > 0) {
        avformat_flush(dctx->avformatContext);

        int re = is_realtime(dctx->avformatContext);
        int64_t tartget_pos = re ? MAX(0, timestamp - 10000000) : timestamp;

        int ret = avformat_seek_file(dctx->avformatContext, -1, INT64_MIN, tartget_pos,
                                     tartget_pos,
                                     AVSEEK_FLAG_BACKWARD);
        if (ret >= 0) {
            if (dctx->videoCodecContext)
                avcodec_flush_buffers(dctx->videoCodecContext);
            if (dctx->audioCodecContext)
                avcodec_flush_buffers(dctx->audioCodecContext);
        }
    }
}

void close_decoder(long decodec_id) {
    LOGE("close_decoder \n");
    if (decodec_id > 0) {
        struct DecoderContext *ctx = (struct DecoderContext *) decodec_id;
        if (ctx->url) {
            free((void *) ctx->url);
            ctx->url = NULL;
        }

        if (ctx->avformatContext) {
            avformat_network_deinit();
            avformat_flush(ctx->avformatContext);
            avformat_close_input(&(ctx->avformatContext));
            avformat_free_context(ctx->avformatContext);
            ctx->avformatContext = NULL;
        }

        if (ctx->audioCodecContext) {
            avcodec_flush_buffers(ctx->audioCodecContext);
            avcodec_free_context(&(ctx->audioCodecContext));
            avcodec_close(ctx->audioCodecContext);
            ctx->audioCodecContext = NULL;
        }

        if (ctx->videoCodecContext) {
            avcodec_flush_buffers(ctx->videoCodecContext);
            avcodec_free_context(&(ctx->videoCodecContext));
            avcodec_close(ctx->videoCodecContext);
            ctx->videoCodecContext = NULL;
        }
        if (ctx->audio_queue) {
            AVFrame *frame = NULL;
            do {
                if (ctx->audio_queue->Size() > 0) {
                    ctx->audio_queue->PopBack(frame);
                }
                av_frame_free(&frame);
            } while (frame != NULL);
            ctx->audio_queue->Close();
        }

        if (ctx->video_queue) {
            AVFrame *frame = NULL;
            do {
                if (ctx->video_queue->Size() > 0) {
                    ctx->video_queue->PopBack(frame);
                }
                av_frame_free(&frame);
            } while (frame != NULL);
            ctx->video_queue->Close();
        }
        free(ctx);
        ctx = NULL;

    }
    LOGE("close_decoder out\n");
}

int av_read_decode_frame(long decodec_id) {
    auto *ctx = (DecoderContext *) decodec_id;
    pthread_create(&ctx->thread_id, 0, av_read_decode_thread, ctx);
}

void *av_read_decode_thread(void *pVoid) {
    int ret = 0;
    if (pVoid) {
        auto *ctx = (struct DecoderContext *) pVoid;
        if (ctx && ctx->avformatContext) {
            for (const auto &cur_item : ctx->vs_decodes) {
                //init
                ctx->decode_frame_ms = 0;
                if (cur_item->startTimeMs > 0) {
                    ctx->start_time = cur_item->startTimeMs;
                    seekToMs(ctx, ctx->offset);
                }
                if (cur_item->endTimeMs > 0)
                    ctx->end_time = cur_item->endTimeMs;

                while (1) {
                    if (ctx->decode_frame_ms > ctx->end_time && ctx->end_time > 0)
                        break;
                    AVPacket *pkg = av_packet_alloc();
                    ret = av_read_frame(ctx->avformatContext, pkg);
                    if (ret == AVERROR_EOF) {
                        LOGE("startDecoder flush \n");
                        av_decode(ctx, NULL, AVMEDIA_TYPE_VIDEO);
                        av_decode(ctx, NULL, AVMEDIA_TYPE_AUDIO);
                        av_packet_free(&pkg);
                        LOGE("av_read_frame eof！\n");
                        break;
                    }

                    enum AVMediaType type = ctx->avformatContext->streams[pkg->stream_index]->codecpar->codec_type;
                    switch (type) {
                        case AVMEDIA_TYPE_AUDIO:
                            if (ctx->disableType == AUDIO) {
                                av_packet_free(&pkg);
                                continue;
                            }
                            break;
                        case AVMEDIA_TYPE_VIDEO:
                            if (ctx->disableType == VIDEO) {
                                av_packet_free(&pkg);
                                continue;
                            }
                            break;
                        default:
                            break;
                    }

                    //对 pts ，dts 校准，因为有偏移
                    if (pkg->dts != AV_NOPTS_VALUE && ctx->avformatContext->start_time > 0)
                        pkg->dts += av_rescale_q(-ctx->avformatContext->start_time, AV_TIME_BASE_Q,
                                                 ctx->avformatContext->streams[pkg->stream_index]->time_base);
                    if (pkg->pts != AV_NOPTS_VALUE && ctx->avformatContext->start_time > 0)
                        pkg->pts += av_rescale_q(-ctx->avformatContext->start_time, AV_TIME_BASE_Q,
                                                 ctx->avformatContext->streams[pkg->stream_index]->time_base);

                    if (ctx->force_Iframe && pkg->flags != AV_PKT_FLAG_KEY &&
                        ctx->avformatContext->streams[pkg->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                        av_packet_free(&pkg);
                        continue;
                    } else {
                        ret = av_decode(ctx, pkg, type);
                        if (ret == -4) {
                            av_packet_free(&pkg);
                            break;
                        }
                    }
                    av_packet_free(&pkg);
                }
            }
        }
        if (ctx->audio_queue){
            LOGE("push empty packet audio ！\n");
            ctx->audio_queue->PushBack(NULL);
        }
        LOGE("push empty packet video ！\n");
        ctx->video_queue->PushBack(NULL);
    }
    LOGE("read thread exit ！\n");
    pthread_exit(NULL);
}
