+#include "FfmpegHelper.h"
+#include "log.h"
+#include <string>
+
+#define FLOGE(...) av_log(NULL, AV_LOG_ERROR, __VA_ARGS__)
+#define FLOGD(...) av_log(NULL, AV_LOG_DEBUG, __VA_ARGS__)
+
+FfmpegHelper* FfmpegHelper::singleton = NULL;
+bool FfmpegHelper::isInit = false;
+
+
+FfmpegHelper::FfmpegHelper(JavaVM *vm, jclass cls)
+: jvm(vm)
+, ai_suanzi_rtmpclient_FfmpegHelper(cls)
+{
+}
+
+
+jint FfmpegHelper::nativeOnLoad(JavaVM * vm, void* reserved)
+{
+ JNIEnv* env;
+ if(vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK){
+ return -1;
+ }
+
+ jclass local_ref = 0;
+ if (env) local_ref = env->FindClass ("ai/suanzi/rtmpclient/FfmpegHelper");
+ jclass global_ref = reinterpret_cast<jclass> (env->NewGlobalRef (local_ref));
+ singleton = new FfmpegHelper(vm, global_ref);
+ return JNI_VERSION_1_6;
+}
+
+void FfmpegHelper::av_log_cb (void *ptr, int level, const char* fmt, va_list vl)
+{
+ static int print_prefix = 1;
+ char line[1024];
+ av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);
+
+ if (level <= AV_LOG_WARNING){
+ if (singleton) singleton->javaPrint(line);
+ } else {
+ if (singleton) singleton->javaPrint(line);
+ //LOGE("%s", line);
+ }
+}
+
+void FfmpegHelper::javaPrint(const char* str)
+{
+ JNIEnv* env = 0;
+ if(this->jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK){
+ return;
+ }
+ jmethodID mid = env->GetStaticMethodID(ai_suanzi_rtmpclient_FfmpegHelper, "javaPrint", "(Ljava/lang/String;)V");
+ jstring jstr = env->NewStringUTF(str);
+ env->CallStaticVoidMethod(ai_suanzi_rtmpclient_FfmpegHelper, mid, jstr);
+ env->DeleteLocalRef(jstr);
+}
+
+void FfmpegHelper::init()
+{
+ av_log_set_callback(av_log_cb);
+ av_log_set_level(AV_LOG_DEBUG);
+ FLOGE("########## Ffmpeg Init ##########");
+ unsigned int v = avutil_version();
+ FLOGE("libavutil - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v));
+ v = avcodec_version();
+ FLOGE("libavcodec - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v));
+ v = avformat_version();
+ FLOGE("libavformat - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v));
+ v = avdevice_version();
+ FLOGE("libavdevice - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v));
+
+ av_register_all();
+ //avdevice_register_all();
+ int ret = 0;
+ if((ret = avformat_network_init()) != 0){
+ FLOGE("avformat_network_init, error:%s(%d)", av_err2str(ret), ret);
+ }
+ isInit = true;
+}
+
+int FfmpegHelper::initEncoder(int width, int height, const char* outpath)
+{
+ if(!isInit) init();
+ FLOGE("initEncoder - width=%d, height=%d, url=%s", width, height, outpath);
+
+ pWidth = width;
+ pHeight = height;
+ int ret = 0;
+
+ avformat_alloc_output_context2(&formatCtx, NULL, "flv", outpath);
+
+ // initial encoder
+ if((codec = avcodec_find_encoder(AV_CODEC_ID_H264)) == NULL){
+ FLOGE("Can not find encoder!\n");
+ return -1;
+ }
+ codecCtx = avcodec_alloc_context3(codec);
+ codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
+ codecCtx->width = width;
+ codecCtx->height = height;
+ codecCtx->time_base.num = 1;
+ codecCtx->time_base.den = 30;
+ codecCtx->bit_rate = 800000;
+ codecCtx->gop_size = 300;
+ if (formatCtx->oformat->flags & AVFMT_GLOBALHEADER) /* Some formats want stream headers to be separate. */
+ codecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
+ //H264 codec param
+ //pCodecCtx->me_range = 16;
+ //pCodecCtx->max_qdiff = 4;
+ //pCodecCtx->qcompress = 0.6;
+ codecCtx->qmin = 10;
+ codecCtx->qmax = 51;
+ //Optional Param
+ codecCtx->max_b_frames = 3;
+ // Set H264 preset and tune
+ AVDictionary *param = 0;
+ av_dict_set(¶m, "preset", "ultrafast", 0);
+ av_dict_set(¶m, "tune", "zerolatency", 0);
+ if ((ret = avcodec_open2(codecCtx, codec, ¶m)) < 0){
+ LOGE("Failed to open encoder!, error:%s(%d)\n", av_err2str(ret), ret);
+ return -1;
+ }
+
+ //Add a new stream to output,should be called by the user before avformat_write_header() for muxing
+ AVStream* vStream;
+ if ((vStream = avformat_new_stream(formatCtx, codec)) == NULL){
+ FLOGE("avformat_new_stream - error");
+ return -1;
+ }
+ vStream->time_base.num = 1;
+ vStream->time_base.den = 30;
+ vStream->codec = codecCtx;
+
+ //Open output URL,set before avformat_write_header() for muxing
+ if (( ret = avio_open(&formatCtx->pb, outpath, AVIO_FLAG_READ_WRITE)) < 0){
+ LOGE("Failed to open output file! error :%s(%d)\n", av_err2str(ret), ret);
+ return -1;
+ }
+
+ if((ret = avformat_write_header(formatCtx, NULL)) != 0){ //Write File Header
+ LOGE("avformat_write_header error :%s(%d)\n", av_err2str(ret), ret);
+ return -1;
+ }
+ startTime = av_gettime();
+ return 0;
+}
+
+int FfmpegHelper::processFrame(uint8_t* data)
+{
+ int ret = 0;
+ AVFrame *pFrame, *pFrameYUV;
+ pFrameYUV = av_frame_alloc();
+ int y_length = pWidth * pHeight;
+ uint8_t *outBuf = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codecCtx->width, codecCtx->height, 1));
+ av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuf, AV_PIX_FMT_YUV420P, codecCtx->width, codecCtx->height, 1);
+
+ memcpy(pFrameYUV->data[0], data, y_length);
+ for (int i = 0; i < y_length / 4; i++){
+ *(pFrameYUV->data[2] + i) = *(data + y_length * 2);
+ *(pFrameYUV->data[1] + i) = *(data + y_length * 2 + 1);
+ }
+
+ pFrameYUV->format = AV_PIX_FMT_YUV420P;
+ pFrameYUV->width = pWidth;
+ pFrameYUV->height = pHeight;
+
+
+ AVPacket encPkt;
+ encPkt.data = NULL;
+ encPkt.size = 0;
+ av_init_packet(&encPkt);
+ int got_frame = 0;
+
+ avcodec_encode_video2(codecCtx, &encPkt, pFrameYUV, &got_frame);
+ av_frame_free(&pFrameYUV);
+
+ if(got_frame == 1){
+ if (frameCnt % (15 * 60) == 0){
+ FLOGE("Succeed to encode frame: %5d\tsize:%5d\n", frameCnt, encPkt.size);
+ }
+ frameCnt++;
+ encPkt.stream_index = vStream->index;
+
+ // PTS
+ AVRational time_base = formatCtx->streams[0]->time_base;//{ 1, 1000 };
+ AVRational r_framerate = {60, 2 };//{ 50, 2 };
+ AVRational time_base_q = { 1, AV_TIME_BASE };
+ //Duration between 2 frames (us)
+ int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate)); //内部时间戳
+ //Parameters
+ //enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));
+ encPkt.pts = av_rescale_q(frameCnt*calc_duration, time_base_q, time_base);
+ encPkt.dts = encPkt.pts;
+ encPkt.duration = av_rescale_q(calc_duration, time_base_q, time_base); //(double)(calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));
+ encPkt.pos = -1;
+
+ //Delay
+ int64_t pts_time = av_rescale_q(encPkt.dts, time_base, time_base_q);
+ int64_t now_time = av_gettime() - startTime;
+ if (pts_time > now_time)
+ av_usleep(pts_time - now_time);
+
+ ret = av_interleaved_write_frame(formatCtx, &encPkt);
+ av_packet_unref(&encPkt);
+ }
+ av_free(outBuf);
+ return 0;
+}
+
+int FfmpegHelper::close()
+{
+ if(vStream)
+ avcodec_close(vStream->codec);
+ avio_close(formatCtx->pb);
+ avformat_free_context(formatCtx);
+ return 0;
+}
+
+
+jint FfmpegHelper::nativeInitialEncoder(JNIEnv *env, jclass cls, jint width, jint height, jstring url)
+{
+ const char* output = env->GetStringUTFChars(url, 0);
+ int ret = 0;
+ if (singleton) ret = singleton->initEncoder(width, height, output);
+ env->ReleaseStringUTFChars(url, output);
+ return ret;
+}
+
+jint FfmpegHelper::nativeProcessFrame(JNIEnv *env, jclass cls, jbyteArray data)
+{
+ jbyte* buf = (jbyte*)env->GetByteArrayElements(data, 0);
+ int ret = 0;
+ if(singleton) ret = singleton->processFrame((uint8_t *)buf);
+ return ret;
+}
+
+jint FfmpegHelper::nativeClose()
+{
+ if(singleton) return singleton->close();
+ return 0;
+}