2011-10-11 08:44:27 -05:00
|
|
|
#include "../include/FFmpegReader.h"
|
|
|
|
|
|
|
|
|
|
using namespace openshot;
|
|
|
|
|
|
|
|
|
|
FFmpegReader::FFmpegReader(string path) throw(InvalidFile, NoStreamsFound, InvalidCodec)
|
2011-10-24 08:22:21 -05:00
|
|
|
: last_video_frame(0), last_audio_frame(0), is_seeking(0), seeking_pts(0), seeking_frame(0),
|
|
|
|
|
audio_pts_offset(999), video_pts_offset(999), working_cache(50), path(path), is_video_seek(true) {
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Open the file (if possible)
|
|
|
|
|
Open();
|
|
|
|
|
|
|
|
|
|
// Get Frame 1 (to determine the offset between the PTS and the Frame Number)
|
|
|
|
|
GetFrame(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FFmpegReader::Open()
|
|
|
|
|
{
|
|
|
|
|
// Register all formats and codecs
|
|
|
|
|
av_register_all();
|
|
|
|
|
|
|
|
|
|
// Open video file
|
|
|
|
|
if (av_open_input_file(&pFormatCtx, path.c_str(), NULL, 0, NULL) != 0)
|
|
|
|
|
throw InvalidFile("File could not be opened.", path);
|
|
|
|
|
|
|
|
|
|
// Retrieve stream information
|
|
|
|
|
if (av_find_stream_info(pFormatCtx) < 0)
|
|
|
|
|
throw NoStreamsFound("No streams found in file.", path);
|
|
|
|
|
|
|
|
|
|
// Dump information about file onto standard error
|
|
|
|
|
//dump_format(pFormatCtx, 0, path.c_str(), 0);
|
|
|
|
|
|
|
|
|
|
videoStream = -1;
|
|
|
|
|
audioStream = -1;
|
|
|
|
|
// Loop through each stream, and identify the video and audio stream index
|
|
|
|
|
for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++)
|
|
|
|
|
{
|
|
|
|
|
// Is this a video stream?
|
|
|
|
|
if (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO && videoStream < 0) {
|
|
|
|
|
videoStream = i;
|
|
|
|
|
}
|
|
|
|
|
// Is this an audio stream?
|
|
|
|
|
if (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO && audioStream < 0) {
|
|
|
|
|
audioStream = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (videoStream == -1 && audioStream == -1)
|
|
|
|
|
throw NoStreamsFound("No video or audio streams found in this file.", path);
|
|
|
|
|
|
|
|
|
|
// Init FileInfo struct (clear all values)
|
|
|
|
|
InitFileInfo();
|
|
|
|
|
|
|
|
|
|
// Is there a video stream?
|
|
|
|
|
if (videoStream != -1)
|
|
|
|
|
{
|
|
|
|
|
// Set the stream index
|
|
|
|
|
info.video_stream_index = videoStream;
|
|
|
|
|
|
|
|
|
|
// Set the codec and codec context pointers
|
|
|
|
|
pStream = pFormatCtx->streams[videoStream];
|
|
|
|
|
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
|
|
|
|
|
|
|
|
|
|
// Find the decoder for the video stream
|
|
|
|
|
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
|
|
|
|
|
if (pCodec == NULL) {
|
|
|
|
|
throw InvalidCodec("A valid video codec could not be found for this file.", path);
|
|
|
|
|
}
|
|
|
|
|
// Open video codec
|
|
|
|
|
if (avcodec_open(pCodecCtx, pCodec) < 0)
|
|
|
|
|
throw InvalidCodec("A video codec was found, but could not be opened.", path);
|
|
|
|
|
|
|
|
|
|
// Update the File Info struct with video details (if a video stream is found)
|
|
|
|
|
UpdateVideoInfo();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Is there an audio stream?
|
|
|
|
|
if (audioStream != -1)
|
|
|
|
|
{
|
|
|
|
|
// Set the stream index
|
|
|
|
|
info.audio_stream_index = audioStream;
|
|
|
|
|
|
|
|
|
|
// Get a pointer to the codec context for the audio stream
|
|
|
|
|
aStream = pFormatCtx->streams[audioStream];
|
|
|
|
|
aCodecCtx = pFormatCtx->streams[audioStream]->codec;
|
|
|
|
|
|
|
|
|
|
// Find the decoder for the audio stream
|
|
|
|
|
aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
|
|
|
|
|
if (aCodec == NULL) {
|
|
|
|
|
throw InvalidCodec("A valid audio codec could not be found for this file.", path);
|
|
|
|
|
}
|
|
|
|
|
// Open audio codec
|
|
|
|
|
if (avcodec_open(aCodecCtx, aCodec) < 0)
|
|
|
|
|
throw InvalidCodec("An audio codec was found, but could not be opened.", path);
|
|
|
|
|
|
|
|
|
|
// Update the File Info struct with audio details (if an audio stream is found)
|
|
|
|
|
UpdateAudioInfo();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FFmpegReader::Close()
|
|
|
|
|
{
|
|
|
|
|
// Delete packet
|
2011-10-24 08:22:21 -05:00
|
|
|
//av_free_packet(&packet);
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Close the codec
|
|
|
|
|
if (info.has_video)
|
|
|
|
|
avcodec_close(pCodecCtx);
|
|
|
|
|
if (info.has_audio)
|
|
|
|
|
avcodec_close(aCodecCtx);
|
|
|
|
|
|
|
|
|
|
// Close the video file
|
|
|
|
|
av_close_input_file(pFormatCtx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FFmpegReader::UpdateAudioInfo()
|
|
|
|
|
{
|
|
|
|
|
// Set values of FileInfo struct
|
|
|
|
|
info.has_audio = true;
|
|
|
|
|
info.file_size = pFormatCtx->file_size;
|
|
|
|
|
info.acodec = aCodecCtx->codec->name;
|
|
|
|
|
info.channels = aCodecCtx->channels;
|
|
|
|
|
info.sample_rate = aCodecCtx->sample_rate;
|
|
|
|
|
info.audio_bit_rate = aCodecCtx->bit_rate;
|
|
|
|
|
|
|
|
|
|
// Timebase of audio stream
|
|
|
|
|
double time_base = Fraction(aStream->time_base.num, aStream->time_base.den).ToDouble();
|
|
|
|
|
info.audio_length = aStream->duration;
|
|
|
|
|
info.duration = info.audio_length * time_base;
|
|
|
|
|
info.audio_timebase.num = aStream->time_base.num;
|
|
|
|
|
info.audio_timebase.den = aStream->time_base.den;
|
2011-10-27 09:40:03 -05:00
|
|
|
|
|
|
|
|
// Set video timebase (if no video stream was found)
|
|
|
|
|
if (!info.has_video)
|
|
|
|
|
{
|
|
|
|
|
// Set a few important default video settings (so audio can be divided into frames)
|
|
|
|
|
double video_time_base = Fraction(1, 30).ToDouble();
|
|
|
|
|
info.video_length = info.duration / video_time_base;
|
|
|
|
|
info.video_timebase.num = 1;
|
|
|
|
|
info.video_timebase.den = 30;
|
|
|
|
|
info.fps.num = 30;
|
|
|
|
|
info.fps.den = 1;
|
|
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FFmpegReader::UpdateVideoInfo()
|
|
|
|
|
{
|
|
|
|
|
// Set values of FileInfo struct
|
|
|
|
|
info.has_video = true;
|
|
|
|
|
info.file_size = pFormatCtx->file_size;
|
|
|
|
|
info.height = pCodecCtx->height;
|
|
|
|
|
info.width = pCodecCtx->width;
|
|
|
|
|
info.vcodec = pCodecCtx->codec->name;
|
|
|
|
|
info.video_bit_rate = pFormatCtx->bit_rate;
|
|
|
|
|
info.fps.num = pStream->r_frame_rate.num;
|
|
|
|
|
info.fps.den = pStream->r_frame_rate.den;
|
|
|
|
|
if (pStream->sample_aspect_ratio.num != 0)
|
|
|
|
|
info.pixel_ratio.num = pStream->sample_aspect_ratio.num;
|
|
|
|
|
else
|
|
|
|
|
info.pixel_ratio.num = 1;
|
|
|
|
|
info.pixel_ratio.den = pStream->sample_aspect_ratio.den;
|
|
|
|
|
info.pixel_format = pCodecCtx->pix_fmt;
|
|
|
|
|
|
|
|
|
|
// Calculate the DAR (display aspect ratio)
|
|
|
|
|
Fraction size(info.width, info.height);
|
|
|
|
|
|
|
|
|
|
// Reduce size fraction
|
|
|
|
|
size.Reduce();
|
|
|
|
|
|
|
|
|
|
// Set the ratio based on the reduced fraction
|
|
|
|
|
info.display_ratio.num = size.num;
|
|
|
|
|
info.display_ratio.den = size.den;
|
|
|
|
|
|
|
|
|
|
// Timebase of video stream
|
|
|
|
|
double time_base = Fraction(pStream->time_base.num, pStream->time_base.den).ToDouble();
|
|
|
|
|
info.video_length = pStream->duration;
|
|
|
|
|
info.duration = info.video_length * time_base;
|
|
|
|
|
info.video_timebase.num = pStream->time_base.num;
|
|
|
|
|
info.video_timebase.den = pStream->time_base.den;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Frame FFmpegReader::GetFrame(int requested_frame)
|
|
|
|
|
{
|
|
|
|
|
// Check the cache for this frame
|
2011-10-14 09:47:05 -05:00
|
|
|
if (final_cache.Exists(requested_frame))
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
|
|
|
|
cout << "Cached Frame!" << endl;
|
|
|
|
|
// Return the cached frame
|
2011-10-14 09:47:05 -05:00
|
|
|
return final_cache.GetFrame(requested_frame);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Frame is not in cache
|
|
|
|
|
// Adjust for a requested frame that is too small or too large
|
|
|
|
|
if (requested_frame < 1)
|
|
|
|
|
requested_frame = 1;
|
|
|
|
|
if (requested_frame > info.video_length)
|
|
|
|
|
requested_frame = info.video_length;
|
|
|
|
|
|
|
|
|
|
// Are we within 20 frames of the requested frame?
|
2011-10-24 08:22:21 -05:00
|
|
|
int diff = requested_frame - last_video_frame;
|
2011-10-11 08:44:27 -05:00
|
|
|
if (abs(diff) >= 0 && abs(diff) <= 19)
|
|
|
|
|
{
|
|
|
|
|
// Continue walking the stream
|
|
|
|
|
cout << " >> CLOSE, SO WALK THE STREAM" << endl;
|
|
|
|
|
return ReadStream(requested_frame);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Greater than 20 frames away, we need to seek to the nearest key frame
|
|
|
|
|
cout << " >> TOO FAR, SO SEEK FIRST AND THEN WALK THE STREAM" << endl;
|
|
|
|
|
Seek(requested_frame);
|
|
|
|
|
|
|
|
|
|
// Then continue walking the stream
|
|
|
|
|
return ReadStream(requested_frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read the stream until we find the requested Frame
|
|
|
|
|
Frame FFmpegReader::ReadStream(int requested_frame)
|
|
|
|
|
{
|
|
|
|
|
// Allocate video frame
|
|
|
|
|
pFrame = avcodec_alloc_frame();
|
2011-10-24 08:22:21 -05:00
|
|
|
bool end_of_stream = false;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
#pragma XXX omp parallel private(i)
|
|
|
|
|
{
|
|
|
|
|
#pragma XXX omp master
|
|
|
|
|
{
|
|
|
|
|
// Loop through the stream until the correct frame is found
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
// Get the next packet (if any)
|
|
|
|
|
if (GetNextPacket() < 0)
|
|
|
|
|
{
|
|
|
|
|
// Break loop when no more packets found
|
|
|
|
|
end_of_stream = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Video packet
|
|
|
|
|
if (packet.stream_index == videoStream)
|
|
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
// Check the status of a seek (if any)
|
|
|
|
|
if (CheckSeek(true))
|
|
|
|
|
// Jump to the next iteration of this loop
|
|
|
|
|
continue;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Check if the AVFrame is finished and set it
|
|
|
|
|
if (GetAVFrame())
|
|
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
cout << endl << "VIDEO PACKET (PTS: " << GetVideoPTS() << ")" << endl;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Update PTS / Frame Offset (if any)
|
|
|
|
|
UpdatePTSOffset(true);
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Process Video Packet
|
|
|
|
|
ProcessVideoPacket(requested_frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
// Audio packet
|
|
|
|
|
else if (packet.stream_index == audioStream)
|
|
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
cout << "AUDIO PACKET (PTS: " << packet.pts << ")" << endl;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Check the status of a seek (if any)
|
|
|
|
|
if (CheckSeek(false))
|
|
|
|
|
// Jump to the next iteration of this loop
|
|
|
|
|
continue;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Update PTS / Frame Offset (if any)
|
|
|
|
|
UpdatePTSOffset(false);
|
|
|
|
|
|
|
|
|
|
// Determine related video frame and starting sample # from audio PTS
|
|
|
|
|
audio_packet_location location = GetAudioPTSLocation(packet.pts);
|
|
|
|
|
|
|
|
|
|
// Process Audio Packet
|
|
|
|
|
ProcessAudioPacket(requested_frame, location.frame, location.sample_start);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Check if working frames are 'finished'
|
|
|
|
|
CheckWorkingFrames(false);
|
|
|
|
|
|
|
|
|
|
// Check if requested 'final' frame is available
|
|
|
|
|
if (final_cache.Exists(requested_frame))
|
|
|
|
|
break;
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
} // end while
|
|
|
|
|
|
|
|
|
|
} // end omp master
|
|
|
|
|
} // end omp parallel
|
|
|
|
|
|
|
|
|
|
// Set flag to not get the next packet (since we already got it)
|
2011-10-14 09:47:05 -05:00
|
|
|
//needs_packet = false;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Delete packet
|
|
|
|
|
//av_free_packet(&packet);
|
|
|
|
|
|
|
|
|
|
// Free the YUV frame
|
|
|
|
|
av_free(pFrame);
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// End of stream? Mark the any other working frames as 'finished'
|
2011-10-26 00:34:48 -05:00
|
|
|
if (end_of_stream)
|
|
|
|
|
CheckWorkingFrames(end_of_stream);
|
2011-10-24 08:22:21 -05:00
|
|
|
|
|
|
|
|
// Return requested frame (if found)
|
|
|
|
|
if (final_cache.Exists(requested_frame))
|
|
|
|
|
// Return prepared frame
|
|
|
|
|
return final_cache.GetFrame(requested_frame);
|
|
|
|
|
else
|
|
|
|
|
// Return blank frame
|
|
|
|
|
return CreateFrame(requested_frame);
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the next packet (if any)
|
|
|
|
|
int FFmpegReader::GetNextPacket()
|
|
|
|
|
{
|
|
|
|
|
// Get the next packet (if any)
|
|
|
|
|
return av_read_frame(pFormatCtx, &packet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get an AVFrame (if any)
|
|
|
|
|
bool FFmpegReader::GetAVFrame()
|
|
|
|
|
{
|
|
|
|
|
// Decode video frame
|
|
|
|
|
int frameFinished = 0;
|
|
|
|
|
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
|
|
|
|
|
packet.data, packet.size);
|
|
|
|
|
|
|
|
|
|
// Did we get a video frame?
|
|
|
|
|
return frameFinished;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check the current seek position and determine if we need to seek again
|
2011-10-24 08:22:21 -05:00
|
|
|
bool FFmpegReader::CheckSeek(bool is_video)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
bool check = false;
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Are we seeking for a specific frame?
|
|
|
|
|
if (is_seeking)
|
|
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
// CHECK VIDEO SEEK?
|
|
|
|
|
if (is_video && is_video_seek)
|
|
|
|
|
check = true;
|
|
|
|
|
// CHECK AUDIO SEEK?
|
|
|
|
|
else if (!is_video && !is_video_seek)
|
|
|
|
|
check = true;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// determine if we are "before" the requested frame
|
2011-10-24 08:22:21 -05:00
|
|
|
if (check)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
if (packet.pts > seeking_pts)
|
|
|
|
|
{
|
|
|
|
|
// SEEKED TOO FAR
|
|
|
|
|
cout << "Woops! Need to seek backwards further..." << endl;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Seek again... to the nearest Keyframe
|
|
|
|
|
Seek(seeking_frame - 5);
|
|
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
else
|
2011-10-24 08:22:21 -05:00
|
|
|
{
|
|
|
|
|
// SEEK WORKED!
|
|
|
|
|
// Seek worked, and we are "before" the requested frame
|
|
|
|
|
is_seeking = false;
|
|
|
|
|
seeking_pts = 0;
|
|
|
|
|
seeking_frame = 0;
|
|
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return the pts to seek to (if any)
|
|
|
|
|
return is_seeking;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process a video packet
|
|
|
|
|
void FFmpegReader::ProcessVideoPacket(int requested_frame)
|
|
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
// Calculate current frame #
|
|
|
|
|
int current_frame = ConvertVideoPTStoFrame(GetVideoPTS());
|
|
|
|
|
last_video_frame = current_frame;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Are we close enough to decode the frame?
|
2011-10-24 08:22:21 -05:00
|
|
|
if ((current_frame) < (requested_frame - 20))
|
2011-10-11 08:44:27 -05:00
|
|
|
// Skip to next frame without decoding or caching
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
#pragma XXX omp task private(current_frame, copyFrame)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
|
|
|
|
AVPicture copyFrame;
|
2011-10-24 08:22:21 -05:00
|
|
|
avpicture_alloc(©Frame, pCodecCtx->pix_fmt, info.width, info.height);
|
|
|
|
|
av_picture_copy(©Frame, (AVPicture *) pFrame, pCodecCtx->pix_fmt, info.width, info.height);
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Process Frame
|
2011-10-24 08:22:21 -05:00
|
|
|
convert_image(current_frame, ©Frame, info.width, info.height, pCodecCtx->pix_fmt);
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Free AVPicture
|
|
|
|
|
avpicture_free(©Frame);
|
|
|
|
|
|
|
|
|
|
} // end omp task
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process an audio packet
|
2011-10-24 08:22:21 -05:00
|
|
|
void FFmpegReader::ProcessAudioPacket(int requested_frame, int target_frame, int starting_sample)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
// Set last audio frame
|
|
|
|
|
last_audio_frame = target_frame;
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Are we close enough to decode the frame's audio?
|
2011-10-24 08:22:21 -05:00
|
|
|
if (target_frame < (requested_frame - 20))
|
2011-10-11 08:44:27 -05:00
|
|
|
// Skip to next frame without decoding or caching
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Allocate audio buffer
|
2011-10-25 17:07:51 -05:00
|
|
|
int16_t audio_buf[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
int packet_samples = 0;
|
|
|
|
|
while (packet.size > 0) {
|
|
|
|
|
// re-initialize buffer size (it gets changed in the avcodec_decode_audio2 method call)
|
2011-10-25 17:07:51 -05:00
|
|
|
int buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// decode audio packet into samples (put samples in the audio_buf array)
|
2011-10-25 00:20:26 -05:00
|
|
|
int used = avcodec_decode_audio2(aCodecCtx, audio_buf, &buf_size,
|
2011-10-11 08:44:27 -05:00
|
|
|
packet.data, packet.size);
|
|
|
|
|
|
2011-10-25 00:20:26 -05:00
|
|
|
if (used < 0) {
|
2011-10-11 08:44:27 -05:00
|
|
|
// Throw exception
|
2011-10-24 08:22:21 -05:00
|
|
|
throw ErrorDecodingAudio("Error decoding audio samples", target_frame);
|
2011-10-11 08:44:27 -05:00
|
|
|
packet.size = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate total number of samples
|
2011-10-25 17:07:51 -05:00
|
|
|
packet_samples += (buf_size / sizeof(int16_t));
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// process samples...
|
2011-10-25 00:20:26 -05:00
|
|
|
packet.data += used;
|
|
|
|
|
packet.size -= used;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int channel_filter = 0; channel_filter < info.channels; channel_filter++)
|
|
|
|
|
{
|
|
|
|
|
// Array of floats (to hold samples for each channel)
|
2011-10-24 08:22:21 -05:00
|
|
|
int starting_frame_number = target_frame;
|
2011-10-11 08:44:27 -05:00
|
|
|
int channel_buffer_size = (packet_samples / info.channels) + 1;
|
|
|
|
|
float *channel_buffer = new float[channel_buffer_size];
|
|
|
|
|
|
|
|
|
|
// Init buffer array
|
|
|
|
|
for (int z = 0; z < channel_buffer_size; z++)
|
|
|
|
|
channel_buffer[z] = 0.0f;
|
|
|
|
|
|
|
|
|
|
// Loop through all samples and add them to our Frame based on channel.
|
|
|
|
|
// Toggle through each channel number, since channel data is stored like (left right left right)
|
|
|
|
|
int channel = 0;
|
2011-10-26 00:34:48 -05:00
|
|
|
int position = 0;
|
2011-10-11 08:44:27 -05:00
|
|
|
for (int sample = 0; sample < packet_samples; sample++)
|
|
|
|
|
{
|
|
|
|
|
// Only add samples for current channel
|
|
|
|
|
if (channel_filter == channel)
|
|
|
|
|
{
|
|
|
|
|
// Add sample (convert from (-32768 to 32768) to (-1.0 to 1.0))
|
|
|
|
|
channel_buffer[position] = audio_buf[sample] * (1.0f / (1 << 15));
|
|
|
|
|
|
|
|
|
|
// Increment audio position
|
|
|
|
|
position++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// increment channel (if needed)
|
2011-10-26 00:34:48 -05:00
|
|
|
if ((channel + 1) < info.channels)
|
2011-10-11 08:44:27 -05:00
|
|
|
// move to next channel
|
|
|
|
|
channel ++;
|
|
|
|
|
else
|
|
|
|
|
// reset channel
|
|
|
|
|
channel = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Get Samples per frame
|
|
|
|
|
int samples_per_frame = GetSamplesPerFrame();
|
|
|
|
|
|
|
|
|
|
// Loop through samples, and add them to the correct frames
|
2011-10-25 17:07:51 -05:00
|
|
|
int start = starting_sample;
|
2011-10-24 08:22:21 -05:00
|
|
|
int remaining_samples = channel_buffer_size;
|
|
|
|
|
while (remaining_samples > 0)
|
|
|
|
|
{
|
|
|
|
|
// Create or get frame object
|
|
|
|
|
Frame f = CreateFrame(starting_frame_number);
|
|
|
|
|
last_audio_frame = starting_frame_number;
|
|
|
|
|
|
|
|
|
|
// Calculate # of samples to add to this frame
|
2011-10-25 17:07:51 -05:00
|
|
|
int samples = samples_per_frame - start;
|
2011-10-24 08:22:21 -05:00
|
|
|
if (samples > remaining_samples)
|
|
|
|
|
samples = remaining_samples;
|
|
|
|
|
|
|
|
|
|
// Add samples for current channel to the frame
|
2011-10-25 17:07:51 -05:00
|
|
|
f.AddAudio(channel_filter, start, channel_buffer, samples, 1.0f);
|
2011-10-24 08:22:21 -05:00
|
|
|
|
|
|
|
|
// Update working cache
|
|
|
|
|
working_cache.Add(starting_frame_number, f);
|
|
|
|
|
|
|
|
|
|
// Decrement remaining samples
|
|
|
|
|
remaining_samples -= samples;
|
|
|
|
|
|
|
|
|
|
// Increment frame number
|
|
|
|
|
starting_frame_number++;
|
|
|
|
|
|
|
|
|
|
// Reset starting sample #
|
2011-10-25 17:07:51 -05:00
|
|
|
start = 0;
|
2011-10-24 08:22:21 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// clear channel buffer
|
|
|
|
|
delete channel_buffer;
|
|
|
|
|
channel_buffer = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Seek to a specific frame. This is not always frame accurate, it's more of an estimation on many codecs.
|
|
|
|
|
void FFmpegReader::Seek(int requested_frame)
|
|
|
|
|
{
|
|
|
|
|
// Adjust for a requested frame that is too small or too large
|
|
|
|
|
if (requested_frame < 1)
|
|
|
|
|
requested_frame = 1;
|
|
|
|
|
if (requested_frame > info.video_length)
|
|
|
|
|
requested_frame = info.video_length;
|
|
|
|
|
|
2011-10-14 09:47:05 -05:00
|
|
|
// Clear working cache (since we are seeking to another location in the file)
|
|
|
|
|
working_cache.Clear();
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Reset the last frame variables
|
|
|
|
|
last_video_frame = 0;
|
|
|
|
|
last_audio_frame = 0;
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Set seeking flags
|
|
|
|
|
is_seeking = true;
|
2011-10-24 08:22:21 -05:00
|
|
|
int64_t seek_target = 0;
|
|
|
|
|
seeking_frame = requested_frame;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Find a valid stream index
|
|
|
|
|
int stream_index = -1;
|
2011-10-27 09:40:03 -05:00
|
|
|
if (info.has_video)
|
2011-10-24 08:22:21 -05:00
|
|
|
{
|
|
|
|
|
// VIDEO SEEK
|
|
|
|
|
is_video_seek = true;
|
2011-10-11 08:44:27 -05:00
|
|
|
stream_index = info.video_stream_index;
|
2011-10-24 08:22:21 -05:00
|
|
|
|
|
|
|
|
// Calculate seek target
|
|
|
|
|
seeking_pts = ConvertFrameToVideoPTS(requested_frame);
|
2011-10-25 00:20:26 -05:00
|
|
|
seek_target = ((double)seeking_pts * info.video_timebase.ToDouble()) * (double)AV_TIME_BASE;
|
2011-10-24 08:22:21 -05:00
|
|
|
}
|
2011-10-27 09:40:03 -05:00
|
|
|
else if (info.has_audio)
|
2011-10-24 08:22:21 -05:00
|
|
|
{
|
|
|
|
|
// AUDIO SEEK
|
|
|
|
|
is_video_seek = false;
|
2011-10-11 08:44:27 -05:00
|
|
|
stream_index = info.audio_stream_index;
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Calculate seek target
|
|
|
|
|
seeking_pts = ConvertFrameToAudioPTS(requested_frame); // PTS seems to always start at zero, and our frame numbers start at 1.
|
2011-10-25 00:20:26 -05:00
|
|
|
seek_target = ((double)seeking_pts * info.audio_timebase.ToDouble()) * (double)AV_TIME_BASE;
|
2011-10-24 08:22:21 -05:00
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// If valid stream, rescale timestamp so the av_seek_frame method will understand it
|
|
|
|
|
if (stream_index >= 0) {
|
|
|
|
|
seek_target = av_rescale_q(seek_target, AV_TIME_BASE_Q,
|
|
|
|
|
pFormatCtx->streams[stream_index]->time_base);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If seeking to frame 1, we need to close and re-open the file (this is more reliable than seeking)
|
|
|
|
|
if (requested_frame == 1)
|
|
|
|
|
{
|
|
|
|
|
// Close and re-open file (basically seeking to frame 1)
|
|
|
|
|
Close();
|
|
|
|
|
Open();
|
|
|
|
|
|
|
|
|
|
// Not actually seeking, so clear these flags
|
|
|
|
|
is_seeking = false;
|
2011-10-24 08:22:21 -05:00
|
|
|
seeking_pts = ConvertFrameToVideoPTS(1);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Seek to nearest key-frame (aka, i-frame)
|
|
|
|
|
if (av_seek_frame(pFormatCtx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) {
|
|
|
|
|
fprintf(stderr, "%s: error while seeking\n", pFormatCtx->filename);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flush buffers
|
2011-10-27 09:40:03 -05:00
|
|
|
if (info.has_video)
|
|
|
|
|
avcodec_flush_buffers(pCodecCtx);
|
|
|
|
|
if (info.has_audio)
|
|
|
|
|
avcodec_flush_buffers(aCodecCtx);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert image to RGB format
|
2011-10-24 08:22:21 -05:00
|
|
|
void FFmpegReader::convert_image(int current_frame, AVPicture *copyFrame, int width, int height, PixelFormat pix_fmt)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
|
|
|
|
AVFrame *pFrameRGB = NULL;
|
|
|
|
|
int numBytes;
|
|
|
|
|
uint8_t *buffer = NULL;
|
|
|
|
|
|
|
|
|
|
// Allocate an AVFrame structure
|
|
|
|
|
pFrameRGB = avcodec_alloc_frame();
|
|
|
|
|
if (pFrameRGB == NULL)
|
|
|
|
|
throw OutOfBoundsFrame("Convert Image Broke!", current_frame, info.video_length);
|
|
|
|
|
|
|
|
|
|
// Determine required buffer size and allocate buffer
|
2011-10-24 08:22:21 -05:00
|
|
|
numBytes = avpicture_get_size(PIX_FMT_RGB24, width, height);
|
2011-10-11 08:44:27 -05:00
|
|
|
buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
|
|
|
|
|
|
|
|
|
|
// Assign appropriate parts of buffer to image planes in pFrameRGB
|
|
|
|
|
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
|
|
|
|
|
// of AVPicture
|
2011-10-24 08:22:21 -05:00
|
|
|
avpicture_fill((AVPicture *) pFrameRGB, buffer, PIX_FMT_RGB24, width, height);
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
struct SwsContext *img_convert_ctx = NULL;
|
|
|
|
|
|
|
|
|
|
// Convert the image into YUV format that SDL uses
|
|
|
|
|
if(img_convert_ctx == NULL) {
|
2011-10-24 08:22:21 -05:00
|
|
|
img_convert_ctx = sws_getContext(width, height, pix_fmt, width, height,
|
2011-10-11 08:44:27 -05:00
|
|
|
PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
|
|
|
|
if(img_convert_ctx == NULL) {
|
|
|
|
|
fprintf(stderr, "Cannot initialize the conversion context!\n");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sws_scale(img_convert_ctx, copyFrame->data, copyFrame->linesize,
|
2011-10-24 08:22:21 -05:00
|
|
|
0, height, pFrameRGB->data, pFrameRGB->linesize);
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Create or get frame object
|
|
|
|
|
Frame f = CreateFrame(current_frame);
|
|
|
|
|
|
|
|
|
|
// Add Image data to frame
|
|
|
|
|
f.AddImage(width, height, "RGB", Magick::CharPixel, buffer);
|
|
|
|
|
|
|
|
|
|
// Update working cache
|
|
|
|
|
working_cache.Add(current_frame, f);
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Free the RGB image
|
|
|
|
|
av_free(buffer);
|
|
|
|
|
av_free(pFrameRGB);
|
2011-10-24 08:22:21 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Get the PTS for the current video packet
|
|
|
|
|
int FFmpegReader::GetVideoPTS()
|
|
|
|
|
{
|
|
|
|
|
int current_pts = -1;
|
|
|
|
|
if(packet.dts != AV_NOPTS_VALUE)
|
|
|
|
|
current_pts = packet.dts;
|
|
|
|
|
|
|
|
|
|
// Return adjusted PTS
|
|
|
|
|
return current_pts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update PTS Offset (if any)
|
|
|
|
|
void FFmpegReader::UpdatePTSOffset(bool is_video)
|
|
|
|
|
{
|
|
|
|
|
// Determine the offset between the PTS and Frame number (only for 1st frame)
|
|
|
|
|
if (is_video)
|
|
|
|
|
{
|
|
|
|
|
// VIDEO PACKET
|
|
|
|
|
if (video_pts_offset == 999) // Has the offset been set yet?
|
|
|
|
|
// Find the difference between PTS and frame number
|
|
|
|
|
video_pts_offset = 1 - GetVideoPTS();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// AUDIO PACKET
|
|
|
|
|
if (audio_pts_offset == 999) // Has the offset been set yet?
|
|
|
|
|
// Find the difference between PTS and frame number
|
|
|
|
|
audio_pts_offset = 1 - packet.pts;
|
|
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert PTS into Frame Number
|
2011-10-24 08:22:21 -05:00
|
|
|
int FFmpegReader::ConvertVideoPTStoFrame(int pts)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2011-10-14 09:47:05 -05:00
|
|
|
// Sometimes the PTS of the 1st frame is not 1 (so we have to adjust)
|
2011-10-24 08:22:21 -05:00
|
|
|
return pts + video_pts_offset;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Convert Frame Number into Video PTS
|
|
|
|
|
int FFmpegReader::ConvertFrameToVideoPTS(int frame_number)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2011-10-24 08:22:21 -05:00
|
|
|
return frame_number - video_pts_offset;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Convert Frame Number into Video PTS
|
|
|
|
|
int FFmpegReader::ConvertFrameToAudioPTS(int frame_number)
|
|
|
|
|
{
|
|
|
|
|
// Get timestamp of this frame
|
|
|
|
|
double seconds = double(frame_number) * info.video_timebase.ToDouble();
|
|
|
|
|
|
|
|
|
|
// Calculate the # of audio packets in this timestamp
|
|
|
|
|
int audio_pts = seconds / info.audio_timebase.ToDouble();
|
|
|
|
|
|
|
|
|
|
// Subtract audio pts offset and return audio PTS
|
|
|
|
|
return audio_pts - audio_pts_offset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate Starting video frame and sample # for an audio PTS
|
|
|
|
|
audio_packet_location FFmpegReader::GetAudioPTSLocation(int pts)
|
2011-10-14 09:47:05 -05:00
|
|
|
{
|
|
|
|
|
// Get the audio packet start time (in seconds)
|
2011-10-24 08:22:21 -05:00
|
|
|
double audio_seconds = double(pts) * info.audio_timebase.ToDouble();
|
2011-10-14 09:47:05 -05:00
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Divide by the video timebase, to get the video frame number (frame # is decimal at this point)
|
|
|
|
|
double frame = audio_seconds / info.video_timebase.ToDouble() + 1;
|
|
|
|
|
|
|
|
|
|
// Frame # as a whole number (no more decimals)
|
|
|
|
|
int whole_frame = int(frame);
|
|
|
|
|
|
|
|
|
|
// Remove the whole number, and only get the decimal of the frame
|
|
|
|
|
double sample_start_percentage = frame - double(whole_frame);
|
|
|
|
|
|
|
|
|
|
// Get Samples per frame
|
|
|
|
|
int samples_per_frame = GetSamplesPerFrame();
|
2011-10-26 14:34:14 -05:00
|
|
|
int sample_start = round(double(samples_per_frame) * sample_start_percentage);
|
2011-10-24 08:22:21 -05:00
|
|
|
|
|
|
|
|
// Prepare final audio packet location
|
|
|
|
|
audio_packet_location location = {whole_frame, sample_start};
|
|
|
|
|
|
|
|
|
|
// Return the associated video frame and starting sample #
|
|
|
|
|
return location;
|
2011-10-14 09:47:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the # of samples per video frame
|
|
|
|
|
int FFmpegReader::GetSamplesPerFrame()
|
|
|
|
|
{
|
|
|
|
|
// Get the number of samples per video frame (sample rate X video timebase)
|
|
|
|
|
return info.sample_rate * info.video_timebase.ToDouble();
|
|
|
|
|
}
|
2011-10-24 08:22:21 -05:00
|
|
|
|
|
|
|
|
// Create a new Frame (or return an existing one) and add it to the working queue.
|
|
|
|
|
Frame FFmpegReader::CreateFrame(int requested_frame)
|
|
|
|
|
{
|
|
|
|
|
// Check working cache
|
|
|
|
|
if (working_cache.Exists(requested_frame))
|
|
|
|
|
// Return existing frame
|
|
|
|
|
return working_cache.GetFrame(requested_frame);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Get Samples per frame
|
|
|
|
|
int samples_per_frame = GetSamplesPerFrame();
|
|
|
|
|
|
|
|
|
|
// Create a new frame on the working cache
|
|
|
|
|
Frame f(requested_frame, info.width, info.height, "#000000", samples_per_frame, info.channels);
|
|
|
|
|
working_cache.Add(requested_frame, f);
|
|
|
|
|
|
|
|
|
|
// Return new frame
|
|
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check the working queue, and move finished frames to the finished queue
|
|
|
|
|
void FFmpegReader::CheckWorkingFrames(bool end_of_stream)
|
|
|
|
|
{
|
|
|
|
|
// Adjust for video only, or audio only
|
|
|
|
|
if (info.video_stream_index < 0)
|
|
|
|
|
last_video_frame = last_audio_frame;
|
|
|
|
|
if (info.audio_stream_index < 0)
|
|
|
|
|
last_audio_frame = last_video_frame;
|
|
|
|
|
|
|
|
|
|
// Loop through all working queue frames
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
// Break if no working frames
|
|
|
|
|
if (working_cache.Count() == 0)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Get the front frame of working cache
|
2011-10-26 00:34:48 -05:00
|
|
|
Frame f = working_cache.GetSmallestFrame();
|
2011-10-24 08:22:21 -05:00
|
|
|
|
|
|
|
|
// Check if working frame is final
|
|
|
|
|
if ((!end_of_stream && f.number <= last_video_frame && f.number < last_audio_frame) || end_of_stream)
|
|
|
|
|
{
|
|
|
|
|
// Move frame to final cache
|
|
|
|
|
final_cache.Add(f.number, f);
|
|
|
|
|
|
|
|
|
|
// Remove frame from working cache
|
2011-10-26 00:34:48 -05:00
|
|
|
working_cache.Remove(f.number);
|
2011-10-24 08:22:21 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
// Stop looping
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|