前语

利用FFmpeg API依据时刻戳定位关键

Android其实原生自带类MediaMetadataRetriever类,也可以依据时刻戳直接获取Bitmap

效果

示例

1.1 获取视频流信息

AVFormatContext * fmt_ctx = avformat_alloc_context();
int ret = avformat_open_input(&fmt_ctx, m_path, nullptr, nullptr);
if (ret < 0 || !fmt_ctx) {
    exit(1);
}
ret = avformat_find_stream_info(fmt_ctx, nullptr);
if (ret < 0) {
    exit(1);
}
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
    if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
        videoIdx = i;
        break;
    }
}
video_parametres = fmt_ctx->streams[videoIdx]->codecpar;
  • avformat_open_input初始化fmt_ctx并翻开获取文件句柄
  • avformat_find_stream_info 查找文件信息流,寻找视频索引

1.2 定位信息

uint8_t *seek_time_to_yuv(double percentage) {
    AVRational time_base = fmt_ctx->streams[videoIdx]->time_base;
    double duration = fmt_ctx->streams[videoIdx]->duration * av_q2d(time_base);
    double timestamp = percentage * duration;
    int64_t pts = timestamp / av_q2d(time_base);
    int ret = av_seek_frame(fmt_ctx, videoIdx, pts, AVSEEK_FLAG_BACKWARD);
    if (ret < 0) {
        return nullptr;
    }
    AVPacket *pkt = av_packet_alloc();
    while (av_read_frame(fmt_ctx, pkt) >= 0) {
        if (pkt->stream_index == videoIdx) {
            //发送到解码器
            ret = avcodec_send_packet(decode_ctx, pkt);
            if (ret < 0) {
                return nullptr;
            }
            AVFrame *seek_frame = av_frame_alloc();
            //取出解码后数据
            ret = avcodec_receive_frame(decode_ctx, seek_frame);
            if (ret < 0) {
                return nullptr;
            }
            int width = video_parametres->width;
            int height = video_parametres->height;
            AVFrame *nv21_frame = av_frame_alloc();
            uint8_t *out_buf = (uint8_t *) av_malloc(
                    av_image_get_buffer_size(AV_PIX_FMT_NV21, width,
                                             height, 1));
            av_image_fill_arrays(nv21_frame->data, nv21_frame->linesize, out_buf,
                                 AV_PIX_FMT_NV21,
                                 width, height, 1);
            SwsContext *sws_ctx = sws_getContext(width, height,
                                                 (AVPixelFormat) video_parametres->format,
                                                 width, height,
                                                 AV_PIX_FMT_NV21, SWS_BICUBIC,
                                                 nullptr, nullptr, nullptr);
            sws_scale(sws_ctx, seek_frame->data, seek_frame->linesize, 0, height,
                      nv21_frame->data, nv21_frame->linesize);
            int y_size = width * height;
            int uv_size = y_size / 4;
            memcpy(out_buf, nv21_frame->data[0], y_size);
            memcpy(out_buf + y_size, nv21_frame->data[1], uv_size * 2);
            av_packet_free(&pkt);
            av_frame_free(&nv21_frame);
            return out_buf;
        }
    }
    return nullptr;
}
  • 核算视频总时长duration这取决于time_base时刻基
  • 依据传入的百分比核算出详细的pts显现的时刻在依据时刻基time_base进行转化到时刻戳
  • av_seek_frame 定位到当时时刻:

    • AVFormatContext *s:输入的AVFormatContext结构体。
    • int stream_index:流的索引。
    • int64_t timestamp:时刻戳。
    • int flags:标志位。

其中,AVFormatContext *s是输入的AVFormatContext结构体,包含了输入文件的格局信息。stream_index是流的索引,用于指定要定位的流。timestamp是时刻戳,用于指定要定位的时刻。flags是标志位,用于指定定位的方法。

  • AVSEEK_FLAG_BACKWARD这个flag标志表明假如没有当时关键帧则定位到前一个
  • avcodec_send_packetavcodec_receive_frame进行解码
  • sws_scale将数据格局转化成 AV_PIX_FMT_NV21

之所以转化成AV_PIX_FMT_NV21因为在Android中YuvImage类支撑NV21格局转化成Bitmap

Github

Android-AddImageWatermarkToVideo