最简单的基于FFmpeg的视频硬解码器-Android

最近在做Android端的ffmpeg硬解码开发,用的ffmpeg4.2.1,发现网上相关资料比较少,大部分都是直接调用Android的MediaCodec进行硬解码的开发,用ffmpeg的比较少。在尝试使用ffmpeg自带的硬解码examples进行改造的过程中也发现有一些问题,一直阻塞着,前后花了两周时间,最终在公司大佬的指点下才调通了这一块。这里简单记录一下。

 

首先是解码函数,这里要注意硬解码使用的是 avcodec_find_decoder_by_name("h264_mediacodec")。解码最后的时候发了一个空包给解码器,空包的意思就是告诉解码器,数据发完了,可以结束了。

JNIEXPORT jint JNICALL Java_com_example_myapplication_NativeUtils_decode
        (JNIEnv *env, jobject obj, jstring input_jstr, jstring output_jstr) {
    AVFormatContext *pFormatCtx = NULL;
    int ret, videoIndex, err_code;
    AVCodecContext *pCodecCtx = NULL;
    AVCodec *pCodec = NULL;
    AVPacket *packet;
    FILE *fp_yuv;
    clock_t time_start, time_finish;
    double time_duration;
    AVStream *video = NULL;
    AVFrame *frame = NULL, *sw_frame = NULL;
    receive_cnt = 0;
    send_cnt = 0;

    char *input_str = (*env)->GetStringUTFChars(env, input_jstr, NULL);
    char *output_str = (*env)->GetStringUTFChars(env, output_jstr, NULL);
    LOGI("----input:%s----\n", input_str);
    LOGI("----output:%s----\n", output_str);

    av_log_set_callback(logcat_log);
    pFormatCtx = avformat_alloc_context();

    if (err_code = avformat_open_input(&pFormatCtx, input_str, NULL, NULL) != 0) {
        LOGE("Couldn't open input stream--- %s: %d\n---", input_str, err_code);
        return -1;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGE("Couldn't find stream information.\n");
        return -1;
    }

    ret = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0);
    if (ret < 0) {
        LOGE("Cannot find a video stream in the input file\n");
        return -1;
    }
    videoIndex = ret;

    //pCodec= avcodec_find_decoder(AV_CODEC_ID_H264);
    pCodec = avcodec_find_decoder_by_name("h264_mediacodec");

    if (!(pCodecCtx = avcodec_alloc_context3(pCodec)))
        return AVERROR(ENOMEM);

    video = pFormatCtx->streams[videoIndex];
    if (avcodec_parameters_to_context(pCodecCtx, video->codecpar) < 0)
        return -1;

    if ((ret = avcodec_open2(pCodecCtx, pCodec, NULL)) < 0) {
        LOGE("----Failed to open codec for stream :%d----\n", ret);
        return -1;
    }

    packet = (AVPacket *) av_malloc(sizeof(AVPacket));

    fp_yuv = fopen(output_str, "wb+");
    if (fp_yuv == NULL) {
        LOGE("----Cannot open output file----\n");
    }

    if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) {
        LOGD("----frame init error----\n");
        return -1;
    }

    time_start = clock();
    int read_frame_ret = 0;
    int read_frame_eof = 0;

    while (1) {
        if (!read_frame_eof) {
            read_frame_ret = av_read_frame(pFormatCtx, packet);
            if (read_frame_ret == AVERROR_EOF) {
                read_frame_eof = 1;
                packet->data = NULL;
                packet->size = 0;
                packet->stream_index = videoIndex;
            } else if (read_frame_ret < 0) {
                av_packet_unref(packet);
                LOGE("----av_read_frame end by error, read_frame_ret:0x%x----\n", read_frame_ret);
                break;
            }

        }

        if (packet && (videoIndex == packet->stream_index)) {
            ret = decode_write(pCodecCtx, packet, fp_yuv, frame, sw_frame);
            if (ret == AVERROR_EOF) {
                av_packet_unref(packet);
                break;
            }
        }
        av_packet_unref(packet);
    }
    LOGD("----av_read_frame end, read_frame_ret:0x%x----\n", read_frame_ret);

    fclose(fp_yuv);
    time_finish = clock();
    time_duration = (double) (time_finish - time_start);
    LOGD("--------time:%fus\n", time_duration);
    av_frame_free(&frame);
    av_frame_free(&sw_frame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    av_buffer_unref(&hw_device_ctx);
    LOGD("----Decode End----\n");
    return 0;
}

 

以下是解码的核心代码,这里最主要的是要理解avcodec_send_packet方法和avcodec_receive_frame方法是异步的。此外AVERROR(EAGAIN)即-11代表过一会再尝试,解码过程中报这个是正常的。

int decode_write(AVCodecContext *avctx, AVPacket *packet, FILE *output_file, AVFrame *frame, AVFrame *sw_frame) {
    int size;
    int tempRet = 0;
    int sendRet = 0;
    int receiveRet = 0;
    int again = 0;
    int readEnd = 0;
    uint8_t *buffer = NULL;

    sendRet = avcodec_send_packet(avctx, packet);
    if (sendRet < 0) {
        LOGD("----avcodec_send_packet error, send_cnt:%d, ret:0x%x----\n", send_cnt, sendRet);
        if (sendRet == AVERROR(EAGAIN)) {
            again = 1;
        }
    } else {
        send_cnt++;
    }
    LOGD("----avcodec_send_packet successful, send_cnt:%d, ret:0x%x----\n", send_cnt, sendRet);

    while (1) {
        receiveRet = avcodec_receive_frame(avctx, frame);

        if ((receiveRet == AVERROR(EAGAIN)) || receiveRet == AVERROR_EOF) {

            if (buffer) {
                av_freep(&buffer);
            }
            LOGD("----avcodec_receive_frame error1, receive_cnt:%d, ret:0x%x----\n", receive_cnt, receiveRet);
            readEnd = 1;
            break;
        } else if (receiveRet < 0) {
            LOGD("----avcodec_receive_frame error2, receive_cnt:%d ret:%d, 0x%x----\n", receive_cnt, receiveRet);
            goto fail;
        }

        if (!readEnd) {
            AVFrame *tmp_frame;
            tmp_frame = frame;
            receive_cnt++;
            LOGD("----avcodec_receive_frame successful, receive_cnt:%d ret:0x%x----\n", receive_cnt, receiveRet);

            size = av_image_get_buffer_size(tmp_frame->format, tmp_frame->width,
                                            tmp_frame->height, 1);
            buffer = av_malloc(size);
            if (!buffer) {
                LOGE("Can not alloc buffer\n");
                tempRet = AVERROR(ENOMEM);
                goto fail;
            }
            tempRet = av_image_copy_to_buffer(buffer, size,
                                              (const uint8_t *const *) tmp_frame->data,
                                              (const int *) tmp_frame->linesize, tmp_frame->format,
                                              tmp_frame->width, tmp_frame->height, 1);
            if (tempRet < 0) {
                LOGE("Can not copy image to buffer\n");
                goto fail;
            }

            if ((tempRet = fwrite(buffer, 1, size, output_file)) < 0) {
                LOGE("----fwrite buffer error!----\n");
                goto fail;
            } else {
                LOGD("----fwrite buffer successful!----\n");
            }
        }

        fail:
        av_freep(&buffer);
        if (tempRet < 0)
            return tempRet;
    }

    if (again) {
        return decode_write(avctx, packet, output_file, frame, sw_frame);
    } else if (readEnd) {
        return receiveRet;
    }
}

 

最后不要忘记实现jni.h里的方法,这是一个固定的方法名,类似android的生命周期,加载jni的时候会自己调用 ,ffmpeg需要靠这个来调用Android的MediaCodec。

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    LOGI("ffmpeg JNI_OnLoad");
    av_jni_set_java_vm(vm, reserved);
    return JNI_VERSION_1_6;
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要下载一个基于FFmpeg SDL的最简单视频播放器,可以按照以下步骤进行: 1. 首先,需要下载和安装FFmpeg软件包。FFmpeg是一个开源的跨平台多媒体框架,可以用于处理音频和视频文件。可以上FFmpeg官网(https://www.ffmpeg.org/)找到相应的下载链接,并根据操作系统选择正确的版本进行下载和安装。 2. 下载SDL库。SDL是一个跨平台的开发库,可以用于创建多媒体应用程序。可以在SDL官网(https://www.libsdl.org/)上找到相应的下载链接,并选择适合自己操作系统的版本进行下载和安装。 3. 使用编程语言(如C/C++)编写一个基于FFmpeg和SDL的视频播放器。可以使用任何喜欢的集成开发环境(IDE),如Visual Studio、Dev-C++等。根据自己的需求,可以封装FFmpeg和SDL的相关函数,以方便播放视频文件。 4. 在编程中,需要包含FFmpeg和SDL所需的头文件,并链接FFmpeg和SDL的库文件。可以在编译选项中添加"-lffmpeg"和"-lsdl"等参数。 5. 编写代码来打开视频文件,读取视频流,将每一帧解码和渲染到屏幕上并进行播放。可以使用FFmpeg提供的函数来进行解码和渲染,使用SDL提供的函数来显示图像并进行窗口管理。 6. 编译和运行程序,即可实现最简单的基于FFmpeg SDL的视频播放器。可以通过命令行输入视频文件的路径进行播放。 需要注意的是,基于FFmpeg SDL的视频播放器可以根据个人需求来进行功能的扩展,如添加播放控制(播放、暂停、停止等)、全屏显示、音量调节等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值