Fix possible packager hangs when reading mp4 from FIFO

For MP4 files, mp4 parser tries to open and read the same file again
to handle trailing MOOV box.

Open FIFO again may result in packager hang.

Update the code to only attempt to load MOOV for local regular files,
as FIFO and remote files are unseekable anyway.

Fixes #664.

Change-Id: Ib286d2876d202cd5a248ffe70b13589b3cc74bc9
This commit is contained in:
KongQun Yang 2019-10-18 15:15:31 -07:00
parent 7973c5396f
commit 0342adb132
11 changed files with 109 additions and 1 deletions

View File

@ -727,6 +727,13 @@ class PackagerFunctionalTest(PackagerAppTest):
self._GetFlags(output_dash=True, output_hls=True)) self._GetFlags(output_dash=True, output_hls=True))
self._CheckTestResults('audio-video-with-language-override-with-subtag') self._CheckTestResults('audio-video-with-language-override-with-subtag')
def testMp4TrailingMoov(self):
self.assertPackageSuccess(
self._GetStreams(['audio', 'video'],
test_files=['bear-640x360-trailing-moov.mp4']),
self._GetFlags(output_dash=True, output_hls=True))
self._CheckTestResults('mp4-trailing-moov')
def testAacHe(self): def testAacHe(self):
self.assertPackageSuccess( self.assertPackageSuccess(
self._GetStreams( self._GetStreams(

View File

@ -0,0 +1,7 @@
#EXTM3U
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_0.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2"
#EXT-X-STREAM-INF:BANDWIDTH=1106817,AVERAGE-BANDWIDTH=1004632,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group"
stream_1.m3u8

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.7360665798187256S">
<Period id="0">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="973483" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
<BaseURL>bear-640x360-trailing-moov-video.mp4</BaseURL>
<SegmentBase indexRange="859-926" timescale="30000">
<Initialization range="0-858"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
<Representation id="1" bandwidth="133334" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>bear-640x360-trailing-moov-audio.mp4</BaseURL>
<SegmentBase indexRange="793-860" timescale="44100">
<Initialization range="0-792"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

@ -0,0 +1,16 @@
#EXTM3U
#EXT-X-VERSION:6
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="bear-640x360-trailing-moov-audio.mp4",BYTERANGE="793@0"
#EXTINF:1.022,
#EXT-X-BYTERANGE:17028@861
bear-640x360-trailing-moov-audio.mp4
#EXTINF:0.998,
#EXT-X-BYTERANGE:16285
bear-640x360-trailing-moov-audio.mp4
#EXTINF:0.720,
#EXT-X-BYTERANGE:9558
bear-640x360-trailing-moov-audio.mp4
#EXT-X-ENDLIST

View File

@ -0,0 +1,16 @@
#EXTM3U
#EXT-X-VERSION:6
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
#EXT-X-TARGETDURATION:2
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="bear-640x360-trailing-moov-video.mp4",BYTERANGE="859@0"
#EXTINF:1.001,
#EXT-X-BYTERANGE:99313@927
bear-640x360-trailing-moov-video.mp4
#EXTINF:1.001,
#EXT-X-BYTERANGE:121807
bear-640x360-trailing-moov-video.mp4
#EXTINF:0.734,
#EXT-X-BYTERANGE:79662
bear-640x360-trailing-moov-video.mp4
#EXT-X-ENDLIST

View File

@ -351,6 +351,31 @@ int64_t File::CopyFile(File* source, File* destination, int64_t max_copy) {
return bytes_copied; return bytes_copied;
} }
bool File::IsLocalRegularFile(const char* file_name) {
base::StringPiece real_file_name;
const FileTypeInfo* file_type = GetFileTypeInfo(file_name, &real_file_name);
DCHECK(file_type);
if (file_type->type != kLocalFilePrefix)
return false;
#if defined(OS_WIN)
const base::FilePath file_path(
base::FilePath::FromUTF8Unsafe(real_file_name));
const DWORD fileattr = GetFileAttributes(file_path.value().c_str());
if (fileattr == INVALID_FILE_ATTRIBUTES) {
LOG(ERROR) << "Failed to GetFileAttributes of " << file_path.value();
return false;
}
return (fileattr & FILE_ATTRIBUTE_DIRECTORY) == 0;
#else
struct stat info;
if (stat(real_file_name.data(), &info) != 0) {
LOG(ERROR) << "Failed to run stat on " << real_file_name;
return false;
}
return S_ISREG(info.st_mode);
#endif
}
std::string File::MakeCallbackFileName( std::string File::MakeCallbackFileName(
const BufferCallbackParams& callback_params, const BufferCallbackParams& callback_params,
const std::string& name) { const std::string& name) {

View File

@ -142,6 +142,10 @@ class File {
/// @return Number of bytes written, or a value < 0 on error. /// @return Number of bytes written, or a value < 0 on error.
static int64_t CopyFile(File* source, File* destination, int64_t max_copy); static int64_t CopyFile(File* source, File* destination, int64_t max_copy);
/// @param file_name is the name of the file to be checked.
/// @return true if `file_name` is a local and regular file.
static bool IsLocalRegularFile(const char* file_name);
/// Generate callback file name. /// Generate callback file name.
/// NOTE: THE GENERATED NAME IS ONLY VAID WHILE @a callback_params IS VALID. /// NOTE: THE GENERATED NAME IS ONLY VAID WHILE @a callback_params IS VALID.
/// @param callback_params references BufferCallbackParams, which will be /// @param callback_params references BufferCallbackParams, which will be

View File

@ -195,6 +195,12 @@ TEST_F(LocalFileTest, WriteFlushCheckSize) {
} }
} }
TEST_F(LocalFileTest, IsLocalReguar) {
ASSERT_EQ(kDataSize,
base::WriteFile(test_file_path_, data_.data(), kDataSize));
ASSERT_TRUE(File::IsLocalRegularFile(local_file_name_.c_str()));
}
class ParamLocalFileTest : public LocalFileTest, class ParamLocalFileTest : public LocalFileTest,
public ::testing::WithParamInterface<uint8_t> {}; public ::testing::WithParamInterface<uint8_t> {};

View File

@ -218,8 +218,12 @@ Status Demuxer::InitializeParser() {
key_source_.get()); key_source_.get());
// Handle trailing 'moov'. // Handle trailing 'moov'.
if (container_name_ == CONTAINER_MOV) if (container_name_ == CONTAINER_MOV &&
File::IsLocalRegularFile(file_name_.c_str())) {
// TODO(kqyang): Investigate whether we can reuse the existing file
// descriptor |media_file_| instead of opening the same file again.
static_cast<mp4::MP4MediaParser*>(parser_.get())->LoadMoov(file_name_); static_cast<mp4::MP4MediaParser*>(parser_.get())->LoadMoov(file_name_);
}
if (!parser_->Parse(buffer_.get(), bytes_read)) { if (!parser_->Parse(buffer_.get(), bytes_read)) {
return Status(error::PARSER_FAILURE, return Status(error::PARSER_FAILURE,
"Cannot parse media file " + file_name_); "Cannot parse media file " + file_name_);