前言
利用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_packet
、avcodec_receive_frame
进行解码sws_scale
将数据格式转换成AV_PIX_FMT_NV21
之所以转换成
AV_PIX_FMT_NV21
因为在Android中YuvImage类支持NV21格式转换成Bitmap