[WebM] Move index segment after init segment

Cues used to be generated in the end of the file; if http range
request is not supported, clients have to download the whole
file to get to the cues.

This CL updated TwoPassSingleSegmentSegmenter to write cues
after writing webm header.

This CL also updates libwebm dependency to latest.

Closes #159

Change-Id: Ic73548e1b872e6b13a37627707e7d0ff3e556877
This commit is contained in:
Kongqun Yang 2016-09-28 17:13:15 -07:00 committed by KongQun Yang
parent ac1d2692cf
commit 97fc9828f0
22 changed files with 202 additions and 174 deletions

2
DEPS
View File

@ -50,7 +50,7 @@ deps = {
Var("chromium_git") + "/chromium/deps/icu@ef5c735307d0f86c7622f69620994c9468beba99",
"src/packager/third_party/libwebm/src":
Var("chromium_git") + "/webm/libwebm@1ad314e297a43966605c4ef23a6442bb58e1d9be",
Var("chromium_git") + "/webm/libwebm@d6af52a1e688fade2e2d22b6d9b0c82f10d38e0b",
"src/packager/third_party/modp_b64":
Var("chromium_git") + "/chromium/src/third_party/modp_b64@aae60754fa997799e8037f5e8ca1f56d58df763d", #405651

View File

@ -6,7 +6,7 @@
<Representation id="0" bandwidth="69362" codecs="vorbis" mimeType="audio/webm" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>output_audio.webm</BaseURL>
<SegmentBase indexRange="23950-23998" timescale="1000000">
<SegmentBase indexRange="4159-4207" timescale="1000000">
<Initialization range="0-4158"/>
</SegmentBase>
</Representation>

View File

@ -6,15 +6,15 @@
<Representation id="0" bandwidth="76531" codecs="opus" mimeType="audio/webm" audioSamplingRate="48000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>output_audio.webm</BaseURL>
<SegmentBase indexRange="26555-26603" timescale="1000000">
<SegmentBase indexRange="323-371" timescale="1000000">
<Initialization range="0-322"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="video" width="320" height="240" frameRate="1000000/34000" par="16:9">
<Representation id="1" bandwidth="203348" codecs="vp9" mimeType="video/webm" sar="427:320">
<Representation id="1" bandwidth="203351" codecs="vp9" mimeType="video/webm" sar="427:320">
<BaseURL>output_video.webm</BaseURL>
<SegmentBase indexRange="69497-69544" timescale="1000000">
<SegmentBase indexRange="299-347" timescale="1000000">
<Initialization range="0-298"/>
</SegmentBase>
</Representation>

View File

@ -8,7 +8,7 @@
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
</ContentProtection>
<BaseURL>output_video.webm</BaseURL>
<SegmentBase indexRange="115225-115274" timescale="1000000">
<SegmentBase indexRange="340-389" timescale="1000000">
<Initialization range="0-339"/>
</SegmentBase>
</Representation>

View File

@ -3,9 +3,9 @@
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.736S">
<Period id="0">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="1000000/33000" par="16:9">
<Representation id="0" bandwidth="335454" codecs="vp8" mimeType="video/webm" sar="1:1">
<Representation id="0" bandwidth="335457" codecs="vp8" mimeType="video/webm" sar="1:1">
<BaseURL>output_video.webm</BaseURL>
<SegmentBase indexRange="114676-114724" timescale="1000000">
<SegmentBase indexRange="289-338" timescale="1000000">
<Initialization range="0-288"/>
</SegmentBase>
</Representation>

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "packager/media/formats/webm/single_segment_segmenter.h"
#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h"
#include <gtest/gtest.h>
@ -39,16 +38,16 @@ const uint8_t kBasicSupportData[] = {
0x53, 0xac, 0x81, 0xb6,
// ID: Seek, Payload Size: 12
0x4d, 0xbb, 0x8c,
// SeekID: binary(4) (Cluster)
0x53, 0xab, 0x84, 0x1f, 0x43, 0xb6, 0x75,
// SeekID: binary(4) (Cues)
0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b,
// SeekPosition: 279
0x53, 0xac, 0x82, 0x01, 0x17,
// ID: Seek, Payload Size: 12
0x4d, 0xbb, 0x8c,
// SeekID: binary(4) (Cues)
0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b,
// SeekPosition: 398
0x53, 0xac, 0x82, 0x01, 0x8e,
// SeekID: binary(4) (Cluster)
0x53, 0xab, 0x84, 0x1f, 0x43, 0xb6, 0x75,
// SeekPosition: 313
0x53, 0xac, 0x82, 0x01, 0x39,
// ID: Void, Payload Size: 24
0xec, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -115,6 +114,28 @@ const uint8_t kBasicSupportData[] = {
0x54, 0xb0, 0x81, 0x64,
// DisplayHeight: 100
0x54, 0xba, 0x81, 0x64,
// ID: Cues, Payload Size: 29
0x1c, 0x53, 0xbb, 0x6b, 0x9d,
// ID: CuePoint, Payload Size: 12
0xbb, 0x8c,
// CueTime: 0
0xb3, 0x81, 0x00,
// ID: CueTrackPositions, Payload Size: 7
0xb7, 0x87,
// CueTrack: 1
0xf7, 0x81, 0x01,
// CueClusterPosition: 313
0xf1, 0x82, 0x01, 0x39,
// ID: CuePoint, Payload Size: 13
0xbb, 0x8d,
// CueTime: 3000
0xb3, 0x82, 0x0b, 0xb8,
// ID: CueTrackPositions, Payload Size: 7
0xb7, 0x87,
// CueTrack: 1
0xf7, 0x81, 0x01,
// CueClusterPosition: 370
0xf1, 0x82, 0x01, 0x72,
// ID: Cluster, Payload Size: 45
0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d,
// Timecode: 0
@ -165,28 +186,6 @@ const uint8_t kBasicSupportData[] = {
0xbf, 0x38, 0x72, 0x20, 0xac,
// BlockDuration: 1000
0x9b, 0x82, 0x03, 0xe8,
// ID: Cues, Payload Size: 29
0x1c, 0x53, 0xbb, 0x6b, 0x9d,
// ID: CuePoint, Payload Size: 12
0xbb, 0x8c,
// CueTime: 0
0xb3, 0x81, 0x00,
// ID: CueTrackPositions, Payload Size: 7
0xb7, 0x87,
// CueTrack: 1
0xf7, 0x81, 0x01,
// CueClusterPosition: 279
0xf1, 0x82, 0x01, 0x17,
// ID: CuePoint, Payload Size: 13
0xbb, 0x8d,
// CueTime: 3000
0xb3, 0x82, 0x0b, 0xb8,
// ID: CueTrackPositions, Payload Size: 7
0xb7, 0x87,
// CueTrack: 1
0xf7, 0x81, 0x01,
// CueClusterPosition: 336
0xf1, 0x82, 0x01, 0x50,
};
} // namespace
@ -200,7 +199,7 @@ class EncrypedSegmenterTest : public SegmentTestBase {
key_source_ =
FixedKeySource::CreateFromHexStrings(kKeyId, kKey, kPsshData, kIv);
ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::SingleSegmentSegmenter>(
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
options, info_.get(), key_source_.get(), &segmenter_));
}

View File

@ -6,19 +6,23 @@
#include "packager/media/formats/webm/seek_head.h"
#include <algorithm>
#include <limits>
#include "packager/base/logging.h"
#include "packager/third_party/libwebm/src/mkvmuxerutil.hpp"
#include "packager/third_party/libwebm/src/webmids.hpp"
namespace shaka {
namespace media {
namespace {
const mkvmuxer::uint64 kElementIds[] = {
mkvmuxer::kMkvInfo, mkvmuxer::kMkvTracks, mkvmuxer::kMkvCluster,
mkvmuxer::kMkvCues,
};
const int kElementIdCount = arraysize(kElementIds);
// Cluster, Cues, Info, Tracks.
const size_t kElementIdCount = 4u;
uint64_t EbmlMasterElementWithPayloadSize(mkvmuxer::MkvId id, uint64_t payload_size) {
return EbmlMasterElementSize(id, payload_size) + payload_size;
}
uint64_t MaxSeekEntrySize() {
const uint64_t max_entry_payload_size =
@ -27,57 +31,46 @@ uint64_t MaxSeekEntrySize() {
static_cast<mkvmuxer::uint64>(std::numeric_limits<uint32_t>::max())) +
EbmlElementSize(mkvmuxer::kMkvSeekPosition,
std::numeric_limits<mkvmuxer::uint64>::max());
const uint64_t max_entry_size =
EbmlMasterElementSize(mkvmuxer::kMkvSeek, max_entry_payload_size) +
max_entry_payload_size;
return max_entry_size;
return EbmlMasterElementWithPayloadSize(mkvmuxer::kMkvSeek,
max_entry_payload_size);
}
} // namespace
SeekHead::SeekHead()
: cluster_pos_(-1),
cues_pos_(-1),
info_pos_(-1),
tracks_pos_(-1),
wrote_void_(false) {}
: total_void_size_(EbmlMasterElementWithPayloadSize(
mkvmuxer::kMkvSeekHead,
kElementIdCount * MaxSeekEntrySize())) {}
SeekHead::~SeekHead() {}
bool SeekHead::Write(mkvmuxer::IMkvWriter* writer) {
std::vector<uint64_t> element_sizes;
const uint64_t payload_size = GetPayloadSize(&element_sizes);
if (payload_size == 0) {
std::vector<SeekElement> seek_elements = CreateSeekElements();
if (seek_elements.empty())
return true;
uint64_t payload_size = 0;
for (const SeekHead::SeekElement& seek_element : seek_elements) {
payload_size +=
seek_element.size +
EbmlMasterElementSize(mkvmuxer::kMkvSeek, seek_element.size);
}
const int64_t start_pos = writer->Position();
if (!WriteEbmlMasterElement(writer, mkvmuxer::kMkvSeekHead, payload_size))
return false;
const int64_t positions[] = {info_pos_, tracks_pos_, cluster_pos_, cues_pos_};
for (int i = 0; i < kElementIdCount; ++i) {
if (element_sizes[i] == 0)
continue;
const mkvmuxer::uint64 position =
static_cast<mkvmuxer::uint64>(positions[i]);
if (!WriteEbmlMasterElement(writer, mkvmuxer::kMkvSeek, element_sizes[i]) ||
!WriteEbmlElement(writer, mkvmuxer::kMkvSeekID, kElementIds[i]) ||
!WriteEbmlElement(writer, mkvmuxer::kMkvSeekPosition, position))
for (const SeekHead::SeekElement& element : seek_elements) {
if (!WriteEbmlMasterElement(writer, mkvmuxer::kMkvSeek, element.size) ||
!WriteEbmlElement(writer, mkvmuxer::kMkvSeekID, element.id) ||
!WriteEbmlElement(writer, mkvmuxer::kMkvSeekPosition, element.position))
return false;
}
// If we wrote void before, then fill in the extra with void.
if (wrote_void_) {
const uint64_t max_payload_size = kElementIdCount * MaxSeekEntrySize();
const uint64_t total_void_size =
EbmlMasterElementSize(mkvmuxer::kMkvSeekHead, max_payload_size) +
max_payload_size;
const uint64_t extra_void =
total_void_size - (writer->Position() - start_pos);
total_void_size_ - (writer->Position() - start_pos);
if (!WriteVoidElement(writer, extra_void))
return false;
}
@ -86,38 +79,36 @@ bool SeekHead::Write(mkvmuxer::IMkvWriter* writer) {
}
bool SeekHead::WriteVoid(mkvmuxer::IMkvWriter* writer) {
const uint64_t payload_size = kElementIdCount * MaxSeekEntrySize();
const uint64_t total_size =
EbmlMasterElementSize(mkvmuxer::kMkvSeekHead, payload_size) +
payload_size;
wrote_void_ = true;
const uint64_t written = WriteVoidElement(writer, total_size);
const uint64_t written = WriteVoidElement(writer, total_void_size_);
if (!written)
return false;
wrote_void_ = true;
return true;
}
uint64_t SeekHead::GetPayloadSize(std::vector<uint64_t>* data) {
const int64_t positions[] = {info_pos_, tracks_pos_, cluster_pos_, cues_pos_};
uint64_t total_payload_size = 0;
data->resize(kElementIdCount);
for (int i = 0; i < kElementIdCount; ++i) {
if (positions[i] < 0) {
(*data)[i] = 0;
continue;
}
std::vector<SeekHead::SeekElement> SeekHead::CreateSeekElements() {
std::vector<SeekHead::SeekElement> seek_elements;
if (info_pos_ != 0)
seek_elements.emplace_back(mkvmuxer::kMkvInfo, info_pos_);
if (tracks_pos_ != 0)
seek_elements.emplace_back(mkvmuxer::kMkvTracks, tracks_pos_);
if (cues_pos_ != 0)
seek_elements.emplace_back(mkvmuxer::kMkvCues, cues_pos_);
if (cluster_pos_ != 0)
seek_elements.emplace_back(mkvmuxer::kMkvCluster, cluster_pos_);
DCHECK_LE(seek_elements.size(), kElementIdCount);
const mkvmuxer::uint64 position =
static_cast<mkvmuxer::uint64>(positions[i]);
(*data)[i] = EbmlElementSize(mkvmuxer::kMkvSeekID, kElementIds[i]) +
EbmlElementSize(mkvmuxer::kMkvSeekPosition, position);
total_payload_size +=
data->at(i) + EbmlMasterElementSize(mkvmuxer::kMkvSeek, data->at(i));
std::sort(seek_elements.begin(), seek_elements.end(),
[](const SeekHead::SeekElement& left,
const SeekHead::SeekElement& right) {
return left.position < right.position;
});
for (SeekHead::SeekElement& element : seek_elements) {
element.size =
EbmlElementSize(mkvmuxer::kMkvSeekID, element.id) +
EbmlElementSize(mkvmuxer::kMkvSeekPosition, element.position);
}
return total_payload_size;
return seek_elements;
}
} // namespace media

View File

@ -10,7 +10,6 @@
#include <stdint.h>
#include <vector>
#include "base/macros.h"
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
namespace shaka {
@ -35,15 +34,29 @@ class SeekHead {
void set_tracks_pos(uint64_t pos) { tracks_pos_ = pos; }
private:
uint64_t GetPayloadSize(std::vector<uint64_t>* data);
SeekHead(const SeekHead&) = delete;
SeekHead& operator=(const SeekHead&) = delete;
int64_t cluster_pos_;
int64_t cues_pos_;
int64_t info_pos_;
int64_t tracks_pos_;
bool wrote_void_;
struct SeekElement {
mkvmuxer::uint64 id;
mkvmuxer::uint64 position;
mkvmuxer::uint64 size;
DISALLOW_COPY_AND_ASSIGN(SeekHead);
SeekElement(uint64_t seek_id, uint64_t seek_position)
: id(seek_id), position(seek_position), size(0) {}
};
// Create seek element vector from positions.
std::vector<SeekElement> CreateSeekElements();
// In practice, these positions, if set, will never be 0, so we use a zero
// value to denote that they are not set.
uint64_t cluster_pos_ = 0;
uint64_t cues_pos_ = 0;
uint64_t info_pos_ = 0;
uint64_t tracks_pos_ = 0;
bool wrote_void_ = false;
const uint64_t total_void_size_ = 0;
};
} // namespace media

View File

@ -36,6 +36,7 @@ class SingleSegmentSegmenter : public Segmenter {
protected:
MkvWriter* writer() { return writer_.get(); }
uint64_t init_end() { return init_end_; }
void set_init_end(uint64_t end) { init_end_ = end; }
void set_index_start(uint64_t start) { index_start_ = start; }
void set_index_end(uint64_t end) { index_end_ = end; }

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "packager/media/formats/webm/single_segment_segmenter.h"
#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h"
#include <gtest/gtest.h>
@ -20,7 +19,7 @@ const uint8_t kBasicSupportData[] = {
// ID: Segment, Payload Size: 343
0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x57,
// ID: SeekHead, Payload Size: 57
0x11, 0x4d, 0x9b, 0x74, 0xb9,
0x11, 0x4d, 0x9b, 0x74, 0xb8,
// ID: Seek, Payload Size: 11
0x4d, 0xbb, 0x8b,
// SeekID: binary(4) (Info)
@ -33,22 +32,22 @@ const uint8_t kBasicSupportData[] = {
0x53, 0xab, 0x84, 0x16, 0x54, 0xae, 0x6b,
// SeekPosition: 182
0x53, 0xac, 0x81, 0xb6,
// ID: Seek, Payload Size: 12
0x4d, 0xbb, 0x8b,
// SeekID: binary(4) (Cues)
0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b,
// SeekPosition: 228
0x53, 0xac, 0x81, 0xe4,
// ID: Seek, Payload Size: 11
0x4d, 0xbb, 0x8b,
// SeekID: binary(4) (Cluster)
0x53, 0xab, 0x84, 0x1f, 0x43, 0xb6, 0x75,
// SeekPosition: 228
0x53, 0xac, 0x81, 0xe4,
// ID: Seek, Payload Size: 12
0x4d, 0xbb, 0x8c,
// SeekID: binary(4) (Cues)
0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b,
// SeekPosition: 325
0x53, 0xac, 0x82, 0x01, 0x45,
// ID: Void, Payload Size: 25
0xec, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// SeekPosition: 246
0x53, 0xac, 0x81, 0xf6,
// ID: Void, Payload Size: 26
0xec, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// ID: Info, Payload Size: 88
0x15, 0x49, 0xa9, 0x66, 0xd8,
// TimecodeScale: 1000000
@ -89,6 +88,18 @@ const uint8_t kBasicSupportData[] = {
0x54, 0xb0, 0x81, 0x64,
// DisplayHeight: 100
0x54, 0xba, 0x81, 0x64,
// ID: Cues, Payload Size: 13
0x1c, 0x53, 0xbb, 0x6b, 0x8d,
// ID: CuePoint, Payload Size: 11
0xbb, 0x8b,
// CueTime: 0
0xb3, 0x81, 0x00,
// ID: CueTrackPositions, Payload Size: 6
0xb7, 0x86,
// CueTrack: 1
0xf7, 0x81, 0x01,
// CueClusterPosition: 246
0xf1, 0x81, 0xf6,
// ID: Cluster, Payload Size: 85
0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55,
// Timecode: 0
@ -117,48 +128,26 @@ const uint8_t kBasicSupportData[] = {
0xa1, 0x89, 0x81, 0x0f, 0xa0, 0x00, 0xde, 0xad, 0xbe, 0xef, 0x00,
// BlockDuration: 1000
0x9b, 0x82, 0x03, 0xe8,
// ID: Cues, Payload Size: 13
0x1c, 0x53, 0xbb, 0x6b, 0x8d,
// ID: CuePoint, Payload Size: 11
0xbb, 0x8b,
// CueTime: 0
0xb3, 0x81, 0x00,
// ID: CueTrackPositions, Payload Size: 6
0xb7, 0x86,
// CueTrack: 1
0xf7, 0x81, 0x01,
// CueClusterPosition: 228
0xf1, 0x81, 0xe4
};
} // namespace
// This is a parameterized test that tests both SingleSegmentSegmenter and
// TwoPassSingleSegmentSegmenter, since they should provide the exact same
// output.
class SingleSegmentSegmenterTest : public SegmentTestBase,
public ::testing::WithParamInterface<bool> {
class SingleSegmentSegmenterTest : public SegmentTestBase {
public:
SingleSegmentSegmenterTest() : info_(CreateVideoStreamInfo()) {}
protected:
void InitializeSegmenter(const MuxerOptions& options) {
if (!GetParam()) {
ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::SingleSegmentSegmenter>(
options, info_.get(), NULL, &segmenter_));
} else {
ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
options, info_.get(), NULL, &segmenter_));
}
ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
options, info_.get(), NULL, &segmenter_));
}
scoped_refptr<StreamInfo> info_;
std::unique_ptr<webm::Segmenter> segmenter_;
};
TEST_P(SingleSegmentSegmenterTest, BasicSupport) {
TEST_F(SingleSegmentSegmenterTest, BasicSupport) {
MuxerOptions options = CreateMuxerOptions();
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
@ -175,7 +164,7 @@ TEST_P(SingleSegmentSegmenterTest, BasicSupport) {
ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData);
}
TEST_P(SingleSegmentSegmenterTest, SplitsClustersOnSegmentDuration) {
TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegmentDuration) {
MuxerOptions options = CreateMuxerOptions();
options.segment_duration = 4.5; // seconds
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
@ -196,7 +185,7 @@ TEST_P(SingleSegmentSegmenterTest, SplitsClustersOnSegmentDuration) {
EXPECT_EQ(3, parser.GetFrameCountForCluster(1));
}
TEST_P(SingleSegmentSegmenterTest, IgnoresFragmentDuration) {
TEST_F(SingleSegmentSegmenterTest, IgnoresFragmentDuration) {
MuxerOptions options = CreateMuxerOptions();
options.fragment_duration = 5; // seconds
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
@ -216,7 +205,7 @@ TEST_P(SingleSegmentSegmenterTest, IgnoresFragmentDuration) {
EXPECT_EQ(8, parser.GetFrameCountForCluster(0));
}
TEST_P(SingleSegmentSegmenterTest, RespectsSAPAlign) {
TEST_F(SingleSegmentSegmenterTest, RespectsSAPAlign) {
MuxerOptions options = CreateMuxerOptions();
options.segment_duration = 3; // seconds
options.segment_sap_aligned = true;
@ -242,9 +231,5 @@ TEST_P(SingleSegmentSegmenterTest, RespectsSAPAlign) {
EXPECT_EQ(4, parser.GetFrameCountForCluster(1));
}
INSTANTIATE_TEST_CASE_P(TrueIsTwoPass,
SingleSegmentSegmenterTest,
::testing::Bool());
} // namespace media
} // namespace shaka

View File

@ -28,6 +28,26 @@ namespace shaka {
namespace media {
namespace webm {
namespace {
// Cues will be inserted before clusters. All clusters will be shifted down by
// the size of cues. However, cluster positions affect the size of cues. This
// function adjusts cues size iteratively until it is stable.
// Returns the size of updated Cues.
uint64_t UpdateCues(mkvmuxer::Cues* cues) {
uint64_t cues_size = cues->Size();
uint64_t adjustment = cues_size;
while (adjustment != 0) {
for (int i = 0; i < cues->cue_entries_size(); ++i) {
mkvmuxer::CuePoint* cue = cues->GetCueByIndex(i);
cue->set_cluster_pos(cue->cluster_pos() + adjustment);
}
uint64_t new_cues_size = cues->Size();
DCHECK_LE(cues_size, new_cues_size);
adjustment = new_cues_size - cues_size;
cues_size = new_cues_size;
}
return cues_size;
}
/// Create the temp file name using process/thread id and current time.
std::string TempFileName(const MuxerOptions& options) {
// TODO: Move to a common util function and remove other uses.
@ -93,17 +113,26 @@ Status TwoPassSingleSegmentSegmenter::DoFinalize() {
if (!cluster()->Finalize())
return Status(error::FILE_FAILURE, "Error finalizing cluster.");
// Write the Cues to the end of the temp file.
uint64_t cues_pos = writer()->Position();
set_index_start(cues_pos);
seek_head()->set_cues_pos(cues_pos - segment_payload_pos());
if (!cues()->Write(writer()))
return Status(error::FILE_FAILURE, "Error writing Cues data.");
const uint64_t header_size = init_end() + 1;
const uint64_t cues_pos = header_size - segment_payload_pos();
const uint64_t cues_size = UpdateCues(cues());
seek_head()->set_cues_pos(cues_pos);
seek_head()->set_cluster_pos(cues_pos + cues_size);
// Write the header to the real output file.
Status temp = WriteSegmentHeader(writer()->Position(), real_writer_.get());
const uint64_t file_size = writer()->Position() + cues_size;
Status temp = WriteSegmentHeader(file_size, real_writer_.get());
if (!temp.ok())
return temp;
DCHECK_EQ(real_writer_->Position(), static_cast<int64_t>(header_size));
// Write the cues to the real output file.
set_index_start(real_writer_->Position());
if (!cues()->Write(real_writer_.get()))
return Status(error::FILE_FAILURE, "Error writing Cues data.");
set_index_end(real_writer_->Position() - 1);
DCHECK_EQ(real_writer_->Position(),
static_cast<int64_t>(segment_payload_pos() + cues_pos + cues_size));
// Close the temp file and open it for reading.
set_writer(std::unique_ptr<MkvWriter>());
@ -113,14 +142,14 @@ Status TwoPassSingleSegmentSegmenter::DoFinalize() {
return Status(error::FILE_FAILURE, "Error opening temp file.");
// Skip the header that has already been written.
uint64_t header_size = real_writer_->Position();
if (!ReadSkip(temp_reader.get(), header_size))
return Status(error::FILE_FAILURE, "Error reading temp file.");
// Copy the rest of the data over.
if (!CopyFileWithClusterRewrite(temp_reader.get(), real_writer_.get(),
cluster()->Size()))
cluster()->Size())) {
return Status(error::FILE_FAILURE, "Error copying temp file.");
}
// Close and delete the temp file.
temp_reader.reset();
@ -128,8 +157,6 @@ Status TwoPassSingleSegmentSegmenter::DoFinalize() {
LOG(WARNING) << "Unable to delete temporary file " << temp_file_name_;
}
// The WebM index is at the end of the file.
set_index_end(real_writer_->file()->Size() - 1);
return real_writer_->Close();
}
@ -175,9 +202,9 @@ bool TwoPassSingleSegmentSegmenter::CopyFileWithClusterRewrite(
if (!ReadSkip(source, cluster_size_size))
return false;
// Copy the remaining data (i.e. Cues data).
// Copy the last cluster.
return dest->WriteFromFile(source) ==
static_cast<int64_t>(last_cluster_payload_size + cues()->Size());
static_cast<int64_t>(last_cluster_payload_size);
}
} // namespace webm

View File

@ -84,6 +84,7 @@
'dependencies': [
'../../../testing/gtest.gyp:gtest',
'../../../testing/gmock.gyp:gmock',
'../../../third_party/libwebm/libwebm.gyp:mkvmuxer',
'../../file/file.gyp:file',
'../../test/media_test.gyp:media_test_support',
'webm',

View File

@ -45,8 +45,6 @@ Status WebMMuxer::Initialize() {
if (!options().single_segment) {
segmenter_.reset(new MultiSegmentSegmenter(options()));
} else if (writer->Seekable()) {
segmenter_.reset(new SingleSegmentSegmenter(options()));
} else {
segmenter_.reset(new TwoPassSingleSegmentSegmenter(options()));
}

View File

@ -12,13 +12,26 @@
'target_name': 'mkvmuxer',
'type': 'static_library',
'sources': [
'src/mkvmuxer.cpp',
'src/mkvmuxer.hpp',
'src/mkvmuxerutil.cpp',
'src/mkvmuxerutil.hpp',
'src/mkvwriter.cpp',
'src/mkvwriter.hpp',
'src/common/webmids.h',
'src/mkvmuxer/mkvmuxer.cc',
'src/mkvmuxer/mkvmuxer.h',
'src/mkvmuxer/mkvmuxertypes.h',
'src/mkvmuxer/mkvmuxerutil.cc',
'src/mkvmuxer/mkvmuxerutil.h',
'src/mkvmuxer/mkvwriter.cc',
'src/mkvmuxer/mkvwriter.h',
'src/mkvmuxer.hpp'
'src/mkvmuxerutil.hpp'
'src/webmids.hpp'
],
'include_dirs': [
'src',
],
'direct_dependent_settings': {
'include_dirs': [
'src',
],
},
},
],
}