Skip to content

Commit 3d0dc4a

Browse files
authored
Merge pull request #284 from cchampet/refactor_VideoPropertiesAnalyseFirstGOP
VideoProperties: improve how to analyse the first GOP
2 parents bcc9741 + 2ccf4b6 commit 3d0dc4a

File tree

8 files changed

+94
-153
lines changed

8 files changed

+94
-153
lines changed

src/AvTranscoder/file/InputFile.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ InputFile::InputFile(const std::string& filename)
3030
_formatContext.findStreamInfo();
3131

3232
// Get the stream information as properties
33-
_properties = new FileProperties(_formatContext);
33+
_properties = new FileProperties(*this);
3434

3535
// Create streams
3636
for(size_t streamIndex = 0; streamIndex < _formatContext.getNbStreams(); ++streamIndex)

src/AvTranscoder/file/InputFile.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class AvExport InputFile
7979

8080
std::string getFilename() const { return _filename; }
8181

82-
FormatContext& getFormatContext() { return _formatContext; }
82+
const FormatContext& getFormatContext() const { return _formatContext; }
8383

8484
/**
8585
* @brief Set the format of the input file

src/AvTranscoder/properties/FileProperties.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
namespace avtranscoder
1212
{
1313

14-
FileProperties::FileProperties(const FormatContext& formatContext)
15-
: _formatContext(&formatContext)
16-
, _avFormatContext(&formatContext.getAVFormatContext())
14+
FileProperties::FileProperties(const InputFile& file)
15+
: _file(file)
16+
, _formatContext(&file.getFormatContext())
17+
, _avFormatContext(&file.getFormatContext().getAVFormatContext())
1718
, _videoStreams()
1819
, _audioStreams()
1920
, _dataStreams()
@@ -31,8 +32,8 @@ FileProperties::FileProperties(const FormatContext& formatContext)
3132
void FileProperties::extractStreamProperties(IProgress& progress, const EAnalyseLevel level)
3233
{
3334
// Returns at the beginning of the stream before any deep analysis
34-
if(level > eAnalyseLevelHeader && !isRawFormat())
35-
const_cast<FormatContext*>(_formatContext)->seek(0, AVSEEK_FLAG_BACKWARD);
35+
if(level > eAnalyseLevelHeader && ! isRawFormat())
36+
const_cast<InputFile&>(_file).seekAtFrame(0, AVSEEK_FLAG_BACKWARD);
3637

3738
// clear properties
3839
clearStreamProperties();
@@ -123,8 +124,8 @@ void FileProperties::extractStreamProperties(IProgress& progress, const EAnalyse
123124
}
124125

125126
// Returns at the beginning of the stream after any deep analysis
126-
if(level > eAnalyseLevelHeader && !isRawFormat())
127-
const_cast<FormatContext*>(_formatContext)->seek(0, AVSEEK_FLAG_BACKWARD);
127+
if(level > eAnalyseLevelHeader && ! isRawFormat())
128+
const_cast<InputFile&>(_file).seekAtFrame(0, AVSEEK_FLAG_BACKWARD);
128129
}
129130

130131
std::string FileProperties::getFilename() const

src/AvTranscoder/properties/FileProperties.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#include <AvTranscoder/common.hpp>
55
#include <AvTranscoder/properties/util.hpp>
6-
#include <AvTranscoder/file/FormatContext.hpp>
6+
#include <AvTranscoder/file/InputFile.hpp>
77
#include <AvTranscoder/progress/IProgress.hpp>
88

99
#include <AvTranscoder/properties/StreamProperties.hpp>
@@ -29,7 +29,7 @@ class AvExport FileProperties
2929
* @note The default streams analyse level is eAnalyseLevelHeader
3030
* @see FormatContext
3131
*/
32-
FileProperties(const FormatContext& formatContext);
32+
FileProperties(const InputFile& file);
3333

3434
/**
3535
* @brief Relaunch streams analysis with a specific level.
@@ -62,7 +62,7 @@ class AvExport FileProperties
6262
size_t getNbAttachementStreams() const { return _attachementStreams.size(); }
6363
size_t getNbUnknownStreams() const { return _unknownStreams.size(); }
6464

65-
const FormatContext& getFormatContext() const { return *_formatContext; }
65+
const InputFile& getInputFile() const { return _file; }
6666

6767
//@{
6868
// @brief Get the properties at the indicated stream index
@@ -110,6 +110,7 @@ class AvExport FileProperties
110110
void clearStreamProperties(); ///< Clear all array of stream properties
111111

112112
private:
113+
const InputFile& _file; ///< Has link (no ownership)
113114
const FormatContext* _formatContext; ///< Has link (no ownership)
114115
const AVFormatContext* _avFormatContext; ///< Has link (no ownership)
115116

src/AvTranscoder/properties/VideoProperties.cpp

Lines changed: 62 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#include "VideoProperties.hpp"
22

3+
#include <AvTranscoder/util.hpp>
4+
#include <AvTranscoder/decoder/VideoDecoder.hpp>
5+
#include <AvTranscoder/data/decoded/VideoFrame.hpp>
36
#include <AvTranscoder/properties/util.hpp>
47
#include <AvTranscoder/properties/FileProperties.hpp>
58
#include <AvTranscoder/progress/NoDisplayProgress.hpp>
6-
#include <AvTranscoder/data/decoded/VideoFrame.hpp>
79

810
extern "C" {
911
#include <libavutil/avutil.h>
@@ -328,78 +330,20 @@ size_t VideoProperties::getBitRate() const
328330
if(_codecContext->bit_rate || _codecContext->rc_max_rate)
329331
return _codecContext->bit_rate;
330332

331-
LOG_WARN("The bitrate of the stream '" << _streamIndex << "' of file '" << _formatContext->filename << "' is unknown.")
332-
LOG_INFO("Compute the video bitrate by decoding the first GOP.")
333-
334-
if(!_codecContext->width || !_codecContext->height)
335-
throw std::runtime_error("cannot compute bit rate: invalid frame size");
336-
337-
if(!_formatContext || !_codec)
338-
throw std::runtime_error("cannot compute bit rate: unknown format or codec");
339-
if(!_codecContext->width || !_codecContext->height)
340-
throw std::runtime_error("cannot compute bit rate: invalid frame size");
341-
342-
// discard no frame type when decode
343-
_codecContext->skip_frame = AVDISCARD_NONE;
344-
345-
AVPacket pkt;
346-
av_init_packet(&pkt);
347-
348-
avcodec_open2(_codecContext, _codec, NULL);
349-
350-
VideoFrame frame(VideoFrameDesc(getWidth(), getHeight(), getPixelProperties().getPixelFormatName()), false);
351-
AVFrame& avFrame = frame.getAVFrame();
352-
353-
int gotFrame = 0;
354-
size_t nbDecodedFrames = 0;
355-
int gopFramesSize = 0;
356-
int positionOfFirstKeyFrame = -1;
357-
int positionOfLastKeyFrame = -1;
358-
359-
while(!av_read_frame(const_cast<AVFormatContext*>(_formatContext), &pkt))
333+
if(_levelAnalysis == eAnalyseLevelHeader)
360334
{
361-
if(pkt.stream_index == (int)_streamIndex)
362-
{
363-
avcodec_decode_video2(_codecContext, &avFrame, &gotFrame, &pkt);
364-
if(gotFrame)
365-
{
366-
// check distance between key frames
367-
if(avFrame.pict_type == AV_PICTURE_TYPE_I)
368-
{
369-
if(positionOfFirstKeyFrame == -1)
370-
positionOfFirstKeyFrame = nbDecodedFrames;
371-
else
372-
positionOfLastKeyFrame = nbDecodedFrames;
373-
}
374-
++nbDecodedFrames;
375-
376-
// added size of all frames of the same gop
377-
if(positionOfLastKeyFrame == -1)
378-
{
379-
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(54, 7, 100)
380-
gopFramesSize += av_frame_get_pkt_size(&avFrame);
381-
#else
382-
gopFramesSize += pkt.size;
383-
#endif
384-
}
385-
}
386-
}
387-
av_free_packet(&pkt);
388-
if(positionOfFirstKeyFrame != -1 && positionOfLastKeyFrame != -1)
389-
break;
335+
LOG_WARN("The bitrate of the stream '" << _streamIndex << "' of file '" << _formatContext->filename << "' is unknown. "
336+
"Need a deeper analysis: see eAnalyseLevelFirstGop.")
337+
return 0;
390338
}
391-
// Close a given AVCodecContext and free all the data associated with it (but not the AVCodecContext itself)
392-
avcodec_close(_codecContext);
393-
// Returns at the beginning of the stream
394-
const_cast<FormatContext*>(&_fileProperties->getFormatContext())->seek(0, AVSEEK_FLAG_BYTE);
395339

396-
const size_t gopSize = positionOfLastKeyFrame - positionOfFirstKeyFrame;
397-
if(gopSize > 0)
340+
LOG_INFO("Estimate the video bitrate from the first GOP.")
341+
size_t gopFramesSize = 0;
342+
for(size_t picture = 0; picture < _gopStructure.size(); ++picture)
398343
{
399-
const float fps = av_q2d(_formatContext->streams[_streamIndex]->avg_frame_rate);
400-
return (gopFramesSize / gopSize) * 8 * fps;
344+
gopFramesSize += _gopStructure.at(picture).second;
401345
}
402-
return 0;
346+
return (gopFramesSize / getGopSize()) * 8 * getFps();
403347
}
404348

405349
size_t VideoProperties::getMaxBitRate() const
@@ -556,69 +500,59 @@ std::vector<std::pair<char, int> > VideoProperties::getGopStructure() const
556500

557501
void VideoProperties::analyseGopStructure(IProgress& progress)
558502
{
559-
if(_formatContext && _codecContext && _codec)
503+
if(! _formatContext || ! _codecContext || ! _codec)
504+
return;
505+
if(! _codecContext->width || ! _codecContext->height)
506+
return;
507+
508+
InputFile& file = const_cast<InputFile&>(_fileProperties->getInputFile());
509+
// Get the stream
510+
IInputStream& stream = file.getStream(_streamIndex);
511+
stream.activate();
512+
// Create a decoder
513+
VideoDecoder decoder(static_cast<InputStream&>(stream));
514+
515+
size_t count = 0;
516+
int positionOfFirstKeyFrame = -1;
517+
int positionOfLastKeyFrame = -1;
518+
VideoFrame frame(VideoFrameDesc(getWidth(), getHeight(), getPixelFormatName(getPixelProperties().getAVPixelFormat())), false);
519+
while(decoder.decodeNextFrame(frame))
560520
{
561-
if(_codecContext->width && _codecContext->height)
521+
AVFrame& avFrame = frame.getAVFrame();
522+
523+
_gopStructure.push_back(
524+
std::make_pair(av_get_picture_type_char(avFrame.pict_type), av_frame_get_pkt_size(&avFrame)));
525+
_isInterlaced = avFrame.interlaced_frame;
526+
_isTopFieldFirst = avFrame.top_field_first;
527+
if(avFrame.pict_type == AV_PICTURE_TYPE_I)
562528
{
563-
// Discard no frame type when decode
564-
_codecContext->skip_frame = AVDISCARD_NONE;
565-
566-
AVPacket pkt;
567-
av_init_packet(&pkt);
568-
569-
// Initialize the AVCodecContext to use the given AVCodec
570-
avcodec_open2(_codecContext, _codec, NULL);
571-
572-
VideoFrame frame(VideoFrameDesc(getWidth(), getHeight(), getPixelProperties().getPixelFormatName()), false);
573-
AVFrame& avFrame = frame.getAVFrame();
574-
575-
size_t count = 0;
576-
int gotFrame = 0;
577-
int positionOfFirstKeyFrame = -1;
578-
int positionOfLastKeyFrame = -1;
579-
580-
while(!av_read_frame(const_cast<AVFormatContext*>(_formatContext), &pkt))
581-
{
582-
if(pkt.stream_index == (int)_streamIndex)
583-
{
584-
avcodec_decode_video2(_codecContext, &avFrame, &gotFrame, &pkt);
585-
if(gotFrame)
586-
{
587-
_gopStructure.push_back(
588-
std::make_pair(av_get_picture_type_char(avFrame.pict_type), av_frame_get_pkt_size(&avFrame)));
589-
_isInterlaced = avFrame.interlaced_frame;
590-
_isTopFieldFirst = avFrame.top_field_first;
591-
if(avFrame.pict_type == AV_PICTURE_TYPE_I)
592-
{
593-
if(positionOfFirstKeyFrame == -1)
594-
positionOfFirstKeyFrame = count;
595-
else
596-
positionOfLastKeyFrame = count;
597-
}
598-
599-
_gopSize = ++count;
600-
}
601-
}
602-
av_free_packet(&pkt);
603-
604-
// If the first 2 key frames are found
605-
if(positionOfFirstKeyFrame != -1 && positionOfLastKeyFrame != -1)
606-
{
607-
// Set gop size as distance between these 2 key frames
608-
_gopSize = positionOfLastKeyFrame - positionOfFirstKeyFrame;
609-
// Update gop structure to keep only one gop
610-
while(_gopStructure.size() > _gopSize)
611-
_gopStructure.pop_back();
612-
break;
613-
}
614-
}
615-
616-
// Close a given AVCodecContext and free all the data associated with it (but not the AVCodecContext itself)
617-
avcodec_close(_codecContext);
618-
619-
// Returns at the beginning of the stream
620-
const_cast<FormatContext*>(&_fileProperties->getFormatContext())->seek(0, AVSEEK_FLAG_BYTE);
529+
if(positionOfFirstKeyFrame == -1)
530+
positionOfFirstKeyFrame = count;
531+
else
532+
positionOfLastKeyFrame = count;
621533
}
534+
535+
_gopSize = ++count;
536+
537+
// If the first 2 key frames are found
538+
if(positionOfFirstKeyFrame != -1 && positionOfLastKeyFrame != -1)
539+
{
540+
// Set gop size as distance between these 2 key frames
541+
_gopSize = positionOfLastKeyFrame - positionOfFirstKeyFrame;
542+
// Update gop structure to keep only one gop
543+
while(_gopStructure.size() > _gopSize)
544+
_gopStructure.pop_back();
545+
break;
546+
}
547+
}
548+
549+
// Returns at the beginning of the stream
550+
file.seekAtFrame(0, AVSEEK_FLAG_BYTE);
551+
552+
// Check GOP size
553+
if(_gopSize <= 0)
554+
{
555+
throw std::runtime_error("Invalid GOP size when decoding the first data.");
622556
}
623557
}
624558

src/AvTranscoder/properties/VideoProperties.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class AvExport VideoProperties : public StreamProperties
4242

4343
/**
4444
* @return The video bitrate in bits/s.
45-
* @warning If there is no such info available in the container, this data is estimated by decoding the first GOP.
45+
* @note 0 if unknown.
46+
* @warning If there is no such info available in the container, this data is estimated using the information of the first GOP.
47+
* @see eAnalyseLevelFirstGop
4648
*/
4749
size_t getBitRate() const;
4850
size_t getMaxBitRate() const;

test/pyTest/testProperties.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -114,25 +114,26 @@ def testCheckRawVideoProperties():
114114
inputFile = av.InputFile(inputFileName)
115115
properties = inputFile.getProperties()
116116

117+
# Check format
117118
assert_true(properties.isRawFormat())
118119
assert_equals(properties.getNbStreams(), 1)
119120
assert_equals(properties.getNbVideoStreams(), 1)
120121
assert_equals(properties.getDuration(), 0) # file duration is unknown
121122
assert_equals(properties.getBitRate(), 0) # file bitrate is unknown
123+
assert_equals(properties.getFileSize(), 256293L)
122124

123-
expectedFileSize = 256293L
124-
assert_equals(properties.getFileSize(), expectedFileSize)
125-
126-
expectedBitRate = 177200L
127-
expectedNbFrames = 200
128-
expectedDuration = 8
129-
expectedFps = 25
130-
125+
# Check video stream when analyse the header
126+
videoStream = properties.getVideoProperties()[0]
127+
assert_equals(videoStream.getFps(), 25)
128+
assert_equals(videoStream.getNbFrames(), 0) # stream nbFrames is unknown
129+
assert_equals(videoStream.getDuration(), 0) # stream duration is unknown
130+
assert_equals(videoStream.getBitRate(), 0) # stream bitrate is unknown
131+
# Check video stream when analyse the first GOP
132+
inputFile.analyse(av.NoDisplayProgress(), av.eAnalyseLevelFirstGop)
131133
videoStream = properties.getVideoProperties()[0]
132-
assert_equals(videoStream.getNbFrames(), expectedNbFrames)
133-
assert_equals(videoStream.getDuration(), expectedDuration)
134-
assert_equals(videoStream.getBitRate(), expectedBitRate)
135-
assert_equals(videoStream.getFps(), expectedFps)
134+
assert_equals(videoStream.getNbFrames(), 200)
135+
assert_equals(videoStream.getDuration(), 8)
136+
assert_equals(videoStream.getBitRate(), 177200L)
136137

137138

138139
def testCheckAudioProperties():

test/pyTest/testTranscoderRewrap.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def testRewrapRawVideoStream():
164164
# get src file of wrap
165165
inputFileName = os.environ['AVTRANSCODER_TEST_VIDEO_RAW_FILE']
166166
src_inputFile = av.InputFile(inputFileName)
167+
src_inputFile.analyse(av.NoDisplayProgress(), av.eAnalyseLevelFirstGop)
167168
src_properties = src_inputFile.getProperties()
168169
src_videoStream = src_properties.getVideoProperties()[0]
169170

@@ -180,6 +181,7 @@ def testRewrapRawVideoStream():
180181

181182
# get dst file of wrap
182183
dst_inputFile = av.InputFile(outputFileName)
184+
dst_inputFile.analyse(av.NoDisplayProgress(), av.eAnalyseLevelFirstGop)
183185
dst_properties = dst_inputFile.getProperties()
184186
dst_videoStream = dst_properties.getVideoProperties()[0]
185187

0 commit comments

Comments
 (0)