[解決済み] av_seek_frame の後に現在の AVFrame の連番を取得するには?
質問
私はデコーダーとFFmpegの初心者です。つまり、あるファイルがあり、フレーム0, 20, 40, 60...を読み込む必要があるのです。
私がやっていることは
AVFrame * m_pAVFrame = nullptr;
int firstFrameIdx = 0;
while(true)
{
if(firstFrameIdx > 0)
{
int64_t seekTarget = FrameToPts(m_pAVStream, firstFrameIdx);
nRet = av_seek_frame(m_pAVFormatCtx, m_streamIdx, seekTarget, AVSEEK_FLAG_FRAME);
}
nRet = av_read_frame(m_pAVFormatCtx, m_pAVPkt);
ret = avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt);
ret = avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame);
firstFrameIdx+=20;
}
しかし、問題は
av_seek_frame
はIframeへのポインタを移動します。例えば、ビデオファイルには0、15、30...のように15個ずつキーフレームがあるとします(もちろん、別の番号でもかまいません)。つまり、フレーム20にシークしようとすると、実際にはフレーム15に到達してしまうということです。
なるほど
AVFrame
は、プロパティ
coded_picture_number
私の場合、これらの返された値をベクターに入れようとしましたが、これらの値は無関係であることがわかりました。
そこで私が期待したのは
0, 15, 30, 45...
例えば、シークしてからフレームを取得して注文番号を聞くことができれば便利なのですが(例:15)、Iframeが15で、フレーム20に到達するために5フレーム読み飛ばして、結果的にフレーム20に到達することは理解できますが、上記のようにシークの後に注文番号を聞いて、次のような変な値が返ってくるのです。
0, 2, 1, 3...
は、どうしようもないのですが...。
基本的な知識が抜けている気がするのですが、どなたかシークロジックの作り方、正しいフレームへのたどり着き方を解説していただけませんか?
アップデイト
初期化ロジック
bool FFmpegDecoder::Init(unsigned char const * pData, int dataSize, int reqId, bool bUseHWAccel, FFmpegDecoderCallback * pCB)
{
Deinit();
// From memory:
if (pData == nullptr || dataSize == 0)
{
printf("FFmpegDecoder::Init FAILED: neither filename nor memory data were given !\n");
return false;
}
m_pIoCtx = std::make_shared<AVIOContextMem>(pData, dataSize);
if (m_pIoCtx->IsValid() == false)
{
printf("FFmpegDecoder::Init FAILED: m_pIoCtx is nullptr !\n");
return false;
}
m_reqId = reqId;
m_bUseHWAccel = bUseHWAccel;
m_pCB = pCB;
m_pData = pData;
m_dataSize = dataSize;
m_bRequestedAbort = false;
m_pAVPkt = av_packet_alloc();
av_init_packet(m_pAVPkt);
m_pAVFrame = av_frame_alloc();
m_pAVFrameRGB = av_frame_alloc();
if (m_bUseHWAccel)
{
m_pSwAVFrameForHw = av_frame_alloc();
}
m_pAVFormatCtx = avformat_alloc_context();
m_pIoCtx->initAVFormatContext(m_pAVFormatCtx);
if (avformat_open_input(&m_pAVFormatCtx, "", nullptr, nullptr) != 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avformat_open_input\n");
return false;
}
if (avformat_find_stream_info(m_pAVFormatCtx, nullptr) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avformat_find_stream_info\n");
return false;
}
//av_dump_format(ctx_format, 0, "", false);
for (int i = 0; i < (int)m_pAVFormatCtx->nb_streams; i++)
{
if (m_pAVFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
m_streamIdx = i;
m_pAVStream = m_pAVFormatCtx->streams[i];
break;
}
}
if (m_pAVStream == nullptr)
{
printf("FFmpegDecoder::InitFFmpeg: failed to find video stream\n");
return false;
}
m_pAVCodec = avcodec_find_decoder(m_pAVStream->codecpar->codec_id);
if (!m_pAVCodec)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_find_decoder\n");
return false;
}
m_pAVCodecCtx = avcodec_alloc_context3(m_pAVCodec);
if (!m_pAVCodecCtx)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_alloc_context3\n");
return false;
}
if (avcodec_parameters_to_context(m_pAVCodecCtx, m_pAVStream->codecpar) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_parameters_to_context\n");
return false;
}
if (m_bUseHWAccel)
{
AVHWDeviceType hwDevType = AV_HWDEVICE_TYPE_DXVA2;
g_hwPixFormat = find_fmt_by_hw_type(hwDevType);
m_pAVCodecCtx->get_format = get_hw_format;
av_opt_set_int(m_pAVCodecCtx, "refcounted_frames", 1, 0);
if (av_hwdevice_ctx_create(&m_pBufferRefForHw, hwDevType, NULL, NULL, 0) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in av_hwdevice_ctx_create\n");
return false;
}
m_pAVCodecCtx->hw_device_ctx = av_buffer_ref(m_pBufferRefForHw);
}
if (avcodec_open2(m_pAVCodecCtx, m_pAVCodec, nullptr) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_open2\n");
return false;
}
m_pAVFrameRGB->format = AV_PIX_FMT_BGR24;
m_pAVFrameRGB->width = m_pAVCodecCtx->width;
m_pAVFrameRGB->height = m_pAVCodecCtx->height;
if (av_frame_get_buffer(m_pAVFrameRGB, 32) != 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in av_frame_get_buffer\n");
return false;
}
m_streamRotationDegrees = GetAVStreamRotation(m_pAVStream);
m_estimatedFramesCount = 0;
assert(m_pAVFormatCtx->nb_streams > 0);
if (m_pAVFormatCtx->nb_streams > 0)
{
m_estimatedFramesCount = m_pAVFormatCtx->streams[0]->nb_frames;
}
//InitConvertColorSpace
// Init converter from YUV420p to BGR:
if (m_bUseHWAccel)
{
m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_NV12, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
}
else
{
m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, m_pAVCodecCtx->pix_fmt, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
}
if (!m_pSwsCtxConvertImg)
{
printf("FFmpegDecoder::InitFFmpeg: error in sws_getContext\n");
return false;
}
m_bInitOK = true;
return true;
}
最新の変更点を含むデコードロジック
void FFmpegDecoder::DecodeWithStep(int step)
{
step = 20;
int currentFramePos = 0;
int number_of_errors = 0;
const int MAX_ERROR_NUM = 10;
while (true)
{
if (step > 0)
{
int seekPos = currentFramePos + step;
int64_t seekTarget = FrameToPts(m_pAVStream, seekPos);
if (av_seek_frame(m_pAVFormatCtx, m_streamIdx, seekTarget, AVSEEK_FLAG_FRAME) < 0)
{
number_of_errors++;
}
else
{
currentFramePos = seekPos;
m_is_seeked = true;
}
}
if (av_read_frame(m_pAVFormatCtx, m_pAVPkt) == 0)
{
if (m_pAVPkt->stream_index == m_streamIdx) //to make sure that I dont get packets from other streams
{
if (m_is_seeked)
{
avcodec_flush_buffers(m_pAVCodecCtx);
m_is_seeked = false;
}
if (avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt) == 0)
{
printf("----- BATCH\n");
while (avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame) == 0)
{
ProcessFrame(m_pAVFrame);
av_frame_unref(m_pAVFrame);
currentFramePos++;
printf("----- cur position (%d) \n", currentFramePos);
}
}
av_packet_unref(m_pAVPkt);
}
}
else
{
number_of_errors++;
}
if (number_of_errors == MAX_ERROR_NUM)
{
printf("EXIT\n");
break;
}
}
}
アップデイト
初期化ロジック
bool FFmpegDecoder::Init(unsigned char const * pData, int dataSize, int reqId, bool bUseHWAccel, FFmpegDecoderCallback * pCB)
{
Deinit();
// From memory:
if (pData == nullptr || dataSize == 0)
{
printf("FFmpegDecoder::Init FAILED: neither filename nor memory data were given !\n");
return false;
}
m_pIoCtx = std::make_shared<AVIOContextMem>(pData, dataSize);
if (m_pIoCtx->IsValid() == false)
{
printf("FFmpegDecoder::Init FAILED: m_pIoCtx is nullptr !\n");
return false;
}
m_reqId = reqId;
m_bUseHWAccel = bUseHWAccel;
m_pCB = pCB;
m_pData = pData;
m_dataSize = dataSize;
m_bRequestedAbort = false;
m_pAVPkt = av_packet_alloc();
av_init_packet(m_pAVPkt);
m_pAVFrame = av_frame_alloc();
m_pAVFrameRGB = av_frame_alloc();
if (m_bUseHWAccel)
{
m_pSwAVFrameForHw = av_frame_alloc();
}
m_pAVFormatCtx = avformat_alloc_context();
m_pIoCtx->initAVFormatContext(m_pAVFormatCtx);
if (avformat_open_input(&m_pAVFormatCtx, "", nullptr, nullptr) != 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avformat_open_input\n");
return false;
}
if (avformat_find_stream_info(m_pAVFormatCtx, nullptr) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avformat_find_stream_info\n");
return false;
}
//av_dump_format(ctx_format, 0, "", false);
for (int i = 0; i < (int)m_pAVFormatCtx->nb_streams; i++)
{
if (m_pAVFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
m_streamIdx = i;
m_pAVStream = m_pAVFormatCtx->streams[i];
break;
}
}
if (m_pAVStream == nullptr)
{
printf("FFmpegDecoder::InitFFmpeg: failed to find video stream\n");
return false;
}
m_pAVCodec = avcodec_find_decoder(m_pAVStream->codecpar->codec_id);
if (!m_pAVCodec)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_find_decoder\n");
return false;
}
m_pAVCodecCtx = avcodec_alloc_context3(m_pAVCodec);
if (!m_pAVCodecCtx)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_alloc_context3\n");
return false;
}
if (avcodec_parameters_to_context(m_pAVCodecCtx, m_pAVStream->codecpar) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_parameters_to_context\n");
return false;
}
if (m_bUseHWAccel)
{
AVHWDeviceType hwDevType = AV_HWDEVICE_TYPE_DXVA2;
g_hwPixFormat = find_fmt_by_hw_type(hwDevType);
m_pAVCodecCtx->get_format = get_hw_format;
av_opt_set_int(m_pAVCodecCtx, "refcounted_frames", 1, 0);
if (av_hwdevice_ctx_create(&m_pBufferRefForHw, hwDevType, NULL, NULL, 0) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in av_hwdevice_ctx_create\n");
return false;
}
m_pAVCodecCtx->hw_device_ctx = av_buffer_ref(m_pBufferRefForHw);
}
if (avcodec_open2(m_pAVCodecCtx, m_pAVCodec, nullptr) < 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in avcodec_open2\n");
return false;
}
m_pAVFrameRGB->format = AV_PIX_FMT_BGR24;
m_pAVFrameRGB->width = m_pAVCodecCtx->width;
m_pAVFrameRGB->height = m_pAVCodecCtx->height;
if (av_frame_get_buffer(m_pAVFrameRGB, 32) != 0)
{
printf("FFmpegDecoder::InitFFmpeg: error in av_frame_get_buffer\n");
return false;
}
m_streamRotationDegrees = GetAVStreamRotation(m_pAVStream);
m_estimatedFramesCount = 0;
assert(m_pAVFormatCtx->nb_streams > 0);
if (m_pAVFormatCtx->nb_streams > 0)
{
m_estimatedFramesCount = m_pAVFormatCtx->streams[0]->nb_frames;
}
//InitConvertColorSpace
// Init converter from YUV420p to BGR:
if (m_bUseHWAccel)
{
m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_NV12, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
}
else
{
m_pSwsCtxConvertImg = sws_getContext(m_pAVCodecCtx->width, m_pAVCodecCtx->height, m_pAVCodecCtx->pix_fmt, m_pAVCodecCtx->width, m_pAVCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
}
if (!m_pSwsCtxConvertImg)
{
printf("FFmpegDecoder::InitFFmpeg: error in sws_getContext\n");
return false;
}
m_bInitOK = true;
return true;
}
void FFmpegDecoder::DecodeWithStep(int step)
{
step = 20;
int currentFramePos = 0;
int number_of_errors = 0;
const int MAX_ERROR_NUM = 10;
int seekPos = 0;
while (true)
{
if (step > 1)
{
seekPos = currentFramePos + step;
int64_t seekTarget = FrameToPts(m_pAVStream, seekPos);
if (av_seek_frame(m_pAVFormatCtx, m_streamIdx, seekTarget, AVSEEK_FLAG_FRAME) < 0)
{
number_of_errors++;
}
else
{
m_is_seeked = true;
}
}
while (true)
{
if (av_read_frame(m_pAVFormatCtx, m_pAVPkt) == 0)
{
if (m_pAVPkt->stream_index == m_streamIdx) //to make sure that I dont get packets from other streams
{
if (m_is_seeked)
{
avcodec_flush_buffers(m_pAVCodecCtx);
m_is_seeked = false;
}
if (avcodec_send_packet(m_pAVCodecCtx, m_pAVPkt) == 0)
{
int ret = 0;
while (ret >= 0)
{
ret = avcodec_receive_frame(m_pAVCodecCtx, m_pAVFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
av_frame_unref(m_pAVFrame);
break;
}
currentFramePos = m_pAVFrame->display_picture_number; //In order to get position of currect frame (seek move poiter to the key frame)
if (currentFramePos < seekPos) //Some frames need to be skiped in order to reach needed frame
{
printf("----- SKIP : cur position (%d) \n", currentFramePos);
av_frame_unref(m_pAVFrame);
continue;
}
ProcessFrame(m_pAVFrame); //needed frame was processed
av_frame_unref(m_pAVFrame);
printf("----- cur position (%d) \n", currentFramePos);
break;
}
}
av_packet_unref(m_pAVPkt);
}
else
{
av_packet_unref(m_pAVPkt); //we got a frame from the wrong stream
}
}
else
{
number_of_errors++;
}
if (number_of_errors == MAX_ERROR_NUM)
{
printf("EXIT1\n");
break;
}
}
if (number_of_errors == MAX_ERROR_NUM)
{
printf("EXIT2\n");
break;
}
}
}
int64_t FrameToPts(AVStream* pavStream, int frame)
{
return (int64_t(frame) * pavStream->r_frame_rate.den * pavStream->time_base.den) /
(int64_t(pavStream->r_frame_rate.num) * pavStream->time_base.num);
}
解決方法は?
特定のフレームに「正確に」シークする...そのため、逆方向のフラグを使用して、そのフレームまたは前のフレームを確実に取得するために、希望するフレームにシークします。前のフレームである場合は、実際に要求されたフレームを取得するまでデコードします。
あなたが見落としている重要なステップが2つあります。
-
各 av_seek_frame (demux) の後、次の avcodec_send_packet (decode) の前に、avcodec_flush_buffers を使用してデコーダをフラッシュする必要があります。
-
各 avcodec_send_packet (デコード) の後、avcodec_receive_frame で全てのフレーム (複数可) を受信する必要があります 例:-)
while (avcodec_receive_frame(...) == 0) { process frame here }
解決策1 (シークなし - 小ステップまたは未知のfpsのため、精度を確保する)
まず、ステップ=1で簡単なデマックスを行ってみましょう。
public void Aleksey()
{
AVPacket* m_pAVPkt = av_packet_alloc();
AVFrame* m_pAVFrame= av_frame_alloc();
int ret;
int step = 1;
int curFrameNumber = 0;
while (true)
{
ret = av_read_frame(demuxer.FormatContext, m_pAVPkt);
if (m_pAVPkt->stream_index != 0) { av_packet_unref(m_pAVPkt); continue; }
ret = avcodec_send_packet(codecCtx, m_pAVPkt);
av_packet_unref(m_pAVPkt);
while (true)
{
ret = avcodec_receive_frame(codecCtx, m_pAVFrame);
if (ret != 0) { av_frame_unref(m_pAVFrame); break; }
curFrameNumber++;
if (curFrameNumber % step == 0)
Console.WriteLine($"[pts: {m_pAVFrame->pts}] [time: {Utils.TicksToTime((long)(m_pAVFrame->pts * demuxer.VideoStreams[0].Timebase))}] [displaynumber: {m_pAVFrame->display_picture_number}] [codednumber: {m_pAVFrame->coded_picture_number}]");
av_frame_unref(m_pAVFrame);
}
}
}
出力(適切な pts/cur フレーム時間が表示されますが、display_picture_number/coded_picture_number でフレーム番号を取得できません)。どうやら m_pAVCodecCtx->frame_number しかし、私はそれが各av_seek_frameとその後avcodec_flush_buffersでリセットされるに違いありません。
[pts: 0] [time: 00:00:00:000] [displaynumber: 0] [codednumber: 0] [framenumber: 1]
[pts: 42] [time: 00:00:00:042] [displaynumber: 0] [codednumber: 3] [framenumber: 2]
[pts: 83] [time: 00:00:00:083] [displaynumber: 0] [codednumber: 2] [framenumber: 3]
[pts: 125] [time: 00:00:00:125] [displaynumber: 0] [codednumber: 4] [framenumber: 4]
[pts: 167] [time: 00:00:00:167] [displaynumber: 0] [codednumber: 1] [framenumber: 5]
[pts: 209] [time: 00:00:00:209] [displaynumber: 0] [codednumber: 7] [framenumber: 6]
[pts: 250] [time: 00:00:00:250] [displaynumber: 0] [codednumber: 6] [framenumber: 7]
[pts: 292] [time: 00:00:00:292] [displaynumber: 0] [codednumber: 8] [framenumber: 8]
[pts: 334] [time: 00:00:00:334] [displaynumber: 0] [codednumber: 5] [framenumber: 9]
[pts: 375] [time: 00:00:00:375] [displaynumber: 0] [codednumber: 11] [framenumber: 10]
このコードは正確にXステップのフレームを進むことを保証しますが、シークを全く使用しないのでパフォーマンスには悪影響です。シークステップが小さい場合は問題ありませんが、大きなステップを使用したい場合は、シーク込みで行くことができます。
解決策2 (シークあり - 性能向上)
AVFormatContext*m_pAVFormatCtx;
AVCodecContext* m_pAVCodecCtx;
AVStream* pavStream;
AVPacket* m_pAVPkt;
AVFrame* m_pAVFrame;
int m_streamIdx;
long startTime;
double avgFrameDuration;
double m_streamTimebase;
public void Prepare()
{
// Using your variable names (mapped with mine)
m_pAVFormatCtx = demuxer.FormatContext;
m_pAVCodecCtx = codecCtx;
pavStream = demuxer.VideoStreams[0].AVStream;
m_streamIdx = pavStream->index;
m_streamTimebase= av_q2d(pavStream->time_base) * 1000.0 * 10000.0; // Convert timebase to ticks so we can easily convert stream's timestamps to ticks
startTime = pavStream->start_time != AV_NOPTS_VALUE ? (long)(pavStream->start_time * m_streamTimebase) : 0; // We will need this when we seek (adding it to seek timestamp)
avgFrameDuration= 10000000 / av_q2d(pavStream->avg_frame_rate); // eg. 1 sec / 25 fps = 400.000 ticks (40ms)
// Prepare packet/frame for demux/decode
m_pAVPkt = av_packet_alloc();
m_pAVFrame = av_frame_alloc();
}
public void GetFrame(int index) // Zero-based frame index
{
int ret;
long frameTimestamp = (long) (index * avgFrameDuration); // Calculation of FrameX timestamp (based on fps/avgFrameDuration)
Console.WriteLine($"Searching for {Utils.TicksToTime(frameTimestamp)}");
// Seeking at frameTimestamp or previous I/Key frame and flushing codec
ret = av_seek_frame(m_pAVFormatCtx, -1, (startTime + frameTimestamp) / 10, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers(m_pAVCodecCtx);
if (ret < 0) return; // handle seek error
while (true)
{
// Demux Packet
ret = av_read_frame(m_pAVFormatCtx, m_pAVPkt);
if (ret != 0) break; // handle EOF/error
if (m_pAVPkt->stream_index != m_streamIdx) { av_packet_unref(m_pAVPkt); continue; } // Exclude other streams
// Send Packet for decoding
ret = avcodec_send_packet(codecCtx, m_pAVPkt);
av_packet_unref(m_pAVPkt);
if (ret != 0) break; // handle EOF/error
while (true)
{
// Receive all available frames for the decoder
ret = avcodec_receive_frame(codecCtx, m_pAVFrame);
if (ret != 0) { av_frame_unref(m_pAVFrame); break; }
// Get frame pts (prefer best_effort_timestamp)
long curPts = m_pAVFrame->best_effort_timestamp == AV_NOPTS_VALUE ? m_pAVFrame->pts : m_pAVFrame->best_effort_timestamp;
if (curPts == AV_NOPTS_VALUE) { { av_frame_unref(m_pAVFrame); continue; } }
// Skip frames before our actual requested frame
Console.WriteLine($"[Skip] [pts: {curPts}] [time: {Utils.TicksToTime((long)(curPts * m_streamTimebase))}]");
if ((long)(curPts * m_streamTimebase) / 10000 < frameTimestamp / 10000) { av_frame_unref(m_pAVFrame); continue; }
Console.WriteLine($"[Found] [pts: {curPts}] [time: {Utils.TicksToTime((long)(curPts * m_streamTimebase))}]");
av_frame_unref(m_pAVFrame);
return;
}
}
}
次のコードでテストします。
Prepare();
GetFrame(100);
GetFrame(200);
GetFrame(300);
GetFrame(100);
出力を与える
Searching for 00:00:04:129
[Skip] [pts: 4004] [time: 00:00:04:004]
[Skip] [pts: 4046] [time: 00:00:04:046]
[Skip] [pts: 4087] [time: 00:00:04:087]
[Skip] [pts: 4129] [time: 00:00:04:129]
[Found] [pts: 4129] [time: 00:00:04:129]
Searching for 00:00:08:299
[Skip] [pts: 8008] [time: 00:00:08:008]
[Skip] [pts: 8050] [time: 00:00:08:050]
[Skip] [pts: 8091] [time: 00:00:08:091]
[Skip] [pts: 8133] [time: 00:00:08:133]
[Skip] [pts: 8175] [time: 00:00:08:175]
[Skip] [pts: 8217] [time: 00:00:08:217]
[Skip] [pts: 8258] [time: 00:00:08:258]
[Skip] [pts: 8300] [time: 00:00:08:300]
[Found] [pts: 8300] [time: 00:00:08:300]
Searching for 00:00:12:470
[Skip] [pts: 12012] [time: 00:00:12:012]
[Skip] [pts: 12054] [time: 00:00:12:054]
[Skip] [pts: 12095] [time: 00:00:12:095]
[Skip] [pts: 12137] [time: 00:00:12:137]
[Skip] [pts: 12179] [time: 00:00:12:179]
[Skip] [pts: 12221] [time: 00:00:12:221]
[Skip] [pts: 12262] [time: 00:00:12:262]
[Skip] [pts: 12304] [time: 00:00:12:304]
[Skip] [pts: 12346] [time: 00:00:12:346]
[Skip] [pts: 12387] [time: 00:00:12:387]
[Skip] [pts: 12429] [time: 00:00:12:429]
[Skip] [pts: 12471] [time: 00:00:12:471]
[Found] [pts: 12471] [time: 00:00:12:471]
Searching for 00:00:04:129
[Skip] [pts: 4004] [time: 00:00:04:004]
[Skip] [pts: 4046] [time: 00:00:04:046]
[Skip] [pts: 4087] [time: 00:00:04:087]
[Skip] [pts: 4129] [time: 00:00:04:129]
[Found] [pts: 4129] [time: 00:00:04:129]
注:2番目の解決策については、小さなステップ(両方とも同じキーフレーム内にあるフレーム)をより良く処理するためのスペースが残っています。最後のシークのキーフレームを保存し、現在のシークのキーフレームと比較することでそれを検証することができます。この場合、コーデックのフラッシュを回避し、ANYフラグを使用して前のフレームの位置で再シークを行い、正確なシークを行います(デコーダを元の位置から続行します)。ただし、新しいフレームのタイムスタンプは、前のフレームより大きくなければなりません。
関連
-
[解決済み】C++のGetlineの問題(オーバーロードされた関数 "getline "のインスタンスがない
-
[解決済み】1つ以上の多重定義されたシンボルが見つかる
-
[解決済み】なぜ、サイズ8の初期化されていない値を使用するのでしょうか?
-
[解決済み] 文字列の単語を反復処理するにはどうすればよいですか?
-
[解決済み] 1ビットのセット、クリア、トグルはどのように行うのですか?
-
[解決済み] C++11では、標準化されたメモリモデルが導入されました。その意味するところは?そして、C++プログラミングにどのような影響を与えるのでしょうか?
-
[解決済み] Linux上で動作するC++コードのプロファイリングを行うにはどうすればよいですか?
-
[解決済み] 型名の後の括弧は、newで違いがあるのでしょうか?
-
[解決済み] FFmpegを使って2つのMP4ファイルを連結する方法は?
-
[解決済み] CまたはC++を使用して、ディレクトリ内のファイルのリストを取得するにはどうすればよいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C-stringを使用すると警告が表示される。"ローカル変数に関連するスタックメモリのアドレスが返される"
-
[解決済み】C++のGetlineの問題(オーバーロードされた関数 "getline "のインスタンスがない
-
[解決済み】 != と =! の違いと例(C++の場合)
-
[解決済み】C++ 式はポインタからオブジェクトへの型を持っている必要があります。
-
[解決済み] string does not name a type Errorが発生するのはなぜですか?
-
[解決済み】変数 '' を抽象型 '' と宣言できない。
-
[解決済み】関数名の前に期待されるイニシャライザー
-
[解決済み】「corrupted size vs. prev_size」glibc エラーを理解する。
-
[解決済み】「std::operator」で「operator<<」にマッチするものがない。
-
[解決済み】浮動小数点数の乱数生成