mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-11-23 03:33:18 +01:00
671 lines
27 KiB
C#
671 lines
27 KiB
C#
/*
|
|
* Copyright 2009 Volker Oth (0xdeadbeef)
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* NOTE: Converted to C# and modified by Nikse.dk@gmail.com
|
|
* NOTE: For more info see http://blog.thescorpius.com/index.php/2017/07/15/presentation-graphic-stream-sup-files-bluray-subtitle-format/
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
|
|
namespace Nikse.SubtitleEdit.Core.BluRaySup
|
|
{
|
|
|
|
public class BluRaySupPicture
|
|
{
|
|
/// <summary>
|
|
/// screen width
|
|
/// </summary>
|
|
public int Width { get; set; }
|
|
|
|
/// <summary>
|
|
/// screen height
|
|
/// </summary>
|
|
public int Height { get; set; }
|
|
|
|
/// <summary>
|
|
/// start time in milliseconds
|
|
/// </summary>
|
|
public long StartTime { get; set; }
|
|
|
|
public int StartTimeForWrite => (int)(StartTime * 90.0);
|
|
|
|
/// <summary>
|
|
/// end time in milliseconds
|
|
/// </summary>
|
|
public long EndTime { get; set; }
|
|
|
|
public int EndTimeForWrite => (int)(EndTime * 90.0);
|
|
|
|
/// <summary>
|
|
/// if true, this is a forced subtitle
|
|
/// </summary>
|
|
public bool IsForced { get; set; }
|
|
|
|
/// <summary>
|
|
/// composition number - increased at start and end PCS
|
|
/// </summary>
|
|
public int CompositionNumber { get; set; }
|
|
|
|
/// <summary>
|
|
/// width of subtitle window (might be larger than image)
|
|
/// </summary>
|
|
public int WindowWidth { get; set; }
|
|
|
|
/// <summary>
|
|
/// height of subtitle window (might be larger than image)
|
|
/// </summary>
|
|
public int WindowHeight { get; set; }
|
|
|
|
/// <summary>
|
|
/// upper left corner of subtitle window x
|
|
/// </summary>
|
|
public int WindowXOffset { get; set; }
|
|
|
|
/// <summary>
|
|
/// upper left corner of subtitle window y
|
|
/// </summary>
|
|
public int WindowYOffset { get; set; }
|
|
|
|
/// <summary>
|
|
/// FPS type (e.g. 0x10 = 24p)
|
|
/// </summary>
|
|
public int FramesPerSecondType { get; set; }
|
|
|
|
/// <summary>
|
|
/// List of (list of) palette info - there are up to 8 palettes per epoch, each can be updated several times
|
|
/// </summary>
|
|
public List<List<PaletteInfo>> Palettes { get; set; }
|
|
|
|
/// <summary>
|
|
/// Create RLE buffer from bitmap
|
|
/// </summary>
|
|
/// <param name="bm">Bitmap to compress</param>
|
|
/// <param name="palette">Palette used for bitmap encoding</param>
|
|
/// <returns>RLE buffer</returns>
|
|
private static byte[] EncodeImage(NikseBitmap bm, Dictionary<Color, int> palette)
|
|
{
|
|
var bytes = new List<byte>();
|
|
for (int y = 0; y < bm.Height; y++)
|
|
{
|
|
int x;
|
|
int len;
|
|
for (x = 0; x < bm.Width; x += len)
|
|
{
|
|
Color c = bm.GetPixel(x, y);
|
|
byte color;
|
|
if (palette.ContainsKey(c))
|
|
{
|
|
color = (byte)palette[c];
|
|
}
|
|
else
|
|
{
|
|
color = FindBestMatch(c, palette);
|
|
}
|
|
|
|
for (len = 1; x + len < bm.Width; len++)
|
|
{
|
|
if (bm.GetPixel(x + len, y) != c)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (len <= 2 && color != 0)
|
|
{
|
|
// only a single occurrence -> add color
|
|
bytes.Add(color);
|
|
if (len == 2)
|
|
{
|
|
bytes.Add(color);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (len > 0x3fff)
|
|
{
|
|
len = 0x3fff;
|
|
}
|
|
|
|
bytes.Add(0); // rle id
|
|
// commented out due to bug in SupRip
|
|
/*if (color == 0 && x+len == bm.Width)
|
|
{
|
|
bytes.Add(0);
|
|
eol = true;
|
|
}
|
|
else */
|
|
if (color == 0 && len < 0x40)
|
|
{
|
|
// 00 xx -> xx times 0
|
|
bytes.Add((byte)len);
|
|
}
|
|
else if (color == 0)
|
|
{
|
|
// 00 4x xx -> xxx zeroes
|
|
bytes.Add((byte)(0x40 | (len >> 8)));
|
|
bytes.Add((byte)len);
|
|
}
|
|
else if (len < 0x40)
|
|
{
|
|
// 00 8x cc -> x times value cc
|
|
bytes.Add((byte)(0x80 | len));
|
|
bytes.Add(color);
|
|
}
|
|
else
|
|
{
|
|
// 00 cx yy cc -> xyy times value cc
|
|
bytes.Add((byte)(0xc0 | (len >> 8)));
|
|
bytes.Add((byte)len);
|
|
bytes.Add(color);
|
|
}
|
|
}
|
|
}
|
|
if (x == bm.Width)
|
|
{
|
|
bytes.Add(0); // rle id
|
|
bytes.Add(0);
|
|
}
|
|
}
|
|
int size = bytes.Count;
|
|
var retval = new byte[size];
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
retval[i] = bytes[i];
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
private static byte FindBestMatch(Color color, Dictionary<Color, int> palette)
|
|
{
|
|
int smallestDiff = 1000;
|
|
int smallestDiffIndex = -1;
|
|
foreach (var kvp in palette)
|
|
{
|
|
int diff = Math.Abs(kvp.Key.A - color.A) + Math.Abs(kvp.Key.R - color.R) + Math.Abs(kvp.Key.G - color.G) + Math.Abs(kvp.Key.B - color.B);
|
|
if (diff < smallestDiff)
|
|
{
|
|
smallestDiff = diff;
|
|
smallestDiffIndex = kvp.Value;
|
|
}
|
|
}
|
|
return (byte)smallestDiffIndex;
|
|
}
|
|
|
|
private static bool HasCloseColor(Color color, Dictionary<Color, int> palette, int maxDifference)
|
|
{
|
|
foreach (var kvp in palette)
|
|
{
|
|
int difference = Math.Abs(kvp.Key.A - color.A) + Math.Abs(kvp.Key.R - color.R) + Math.Abs(kvp.Key.G - color.G) + Math.Abs(kvp.Key.B - color.B);
|
|
if (difference < maxDifference)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static Dictionary<Color, int> GetBitmapPalette(NikseBitmap bitmap)
|
|
{
|
|
var pal = new Dictionary<Color, int>();
|
|
for (int y = 0; y < bitmap.Height; y++)
|
|
{
|
|
for (int x = 0; x < bitmap.Width; x++)
|
|
{
|
|
var c = bitmap.GetPixel(x, y);
|
|
if (c != Color.Transparent)
|
|
{
|
|
if (pal.Count < 100)
|
|
{
|
|
if (!HasCloseColor(c, pal, 1))
|
|
{
|
|
pal.Add(c, pal.Count);
|
|
}
|
|
}
|
|
else if (pal.Count < 240)
|
|
{
|
|
if (!HasCloseColor(c, pal, 5))
|
|
{
|
|
pal.Add(c, pal.Count);
|
|
}
|
|
}
|
|
else if (pal.Count < 254 && !HasCloseColor(c, pal, 25))
|
|
{
|
|
pal.Add(c, pal.Count);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pal.Add(Color.Transparent, pal.Count); // last entry must be transparent
|
|
return pal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get ID for given frame rate
|
|
/// </summary>
|
|
/// <param name="fps">frame rate</param>
|
|
/// <returns>byte ID for the given frame rate</returns>
|
|
private static int GetFpsId(double fps)
|
|
{
|
|
if (Math.Abs(fps - Core.Fps24Hz) < 0.01) // 24
|
|
{
|
|
return 0x20;
|
|
}
|
|
|
|
if (Math.Abs(fps - Core.FpsPal) < 0.01) // 25
|
|
{
|
|
return 0x30;
|
|
}
|
|
|
|
if (Math.Abs(fps - Core.FpsNtsc) < 0.01) // 29.97
|
|
{
|
|
return 0x40;
|
|
}
|
|
|
|
if (Math.Abs(fps - Core.FpsPalI) < 0.01) // 50
|
|
{
|
|
return 0x60;
|
|
}
|
|
|
|
if (Math.Abs(fps - Core.FpsNtscI) < 0.1) // 59.94
|
|
{
|
|
return 0x70;
|
|
}
|
|
|
|
return 0x10; // 23.976
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the binary stream representation of one caption
|
|
/// </summary>
|
|
/// <param name="pic">SubPicture object containing caption info - note that first Composition Number should be 0, then 2, 4, 8, etc.</param>
|
|
/// <param name="bmp">Bitmap</param>
|
|
/// <param name="fps">Frames per second</param>
|
|
/// <param name="bottomMargin">Image bottom margin</param>
|
|
/// <param name="leftOrRightMargin">Image left/right margin</param>
|
|
/// <param name="alignment">Alignment of image</param>
|
|
/// <param name="overridePosition">Position that overrides alignment</param>
|
|
/// <returns>Byte buffer containing the binary stream representation of one caption</returns>
|
|
public static byte[] CreateSupFrame(BluRaySupPicture pic, Bitmap bmp, double fps, int bottomMargin, int leftOrRightMargin, ContentAlignment alignment, Point? overridePosition = null)
|
|
{
|
|
var bm = new NikseBitmap(bmp);
|
|
var colorPalette = GetBitmapPalette(bm);
|
|
var pal = new BluRaySupPalette(colorPalette.Count);
|
|
int k = 0;
|
|
foreach (var kvp in colorPalette)
|
|
{
|
|
pal.SetColor(k, kvp.Key);
|
|
k++;
|
|
}
|
|
|
|
byte[] rleBuf = EncodeImage(bm, colorPalette);
|
|
|
|
// for some obscure reason, a packet can be a maximum 0xfffc bytes
|
|
// since 13 bytes are needed for the header("PG", PTS, DTS, ID, SIZE)
|
|
// there are only 0xffef bytes available for the packet
|
|
// since the first ODS packet needs an additional 11 bytes for info
|
|
// and the following ODS packets need 4 additional bytes, the
|
|
// first package can store only 0xffe4 RLE buffer bytes and the
|
|
// following packets can store 0xffeb RLE buffer bytes
|
|
int numAddPackets;
|
|
if (rleBuf.Length <= 0xffe4)
|
|
{
|
|
numAddPackets = 0; // no additional packets needed;
|
|
}
|
|
else
|
|
{
|
|
numAddPackets = 1 + (rleBuf.Length - 0xffe4) / 0xffeb;
|
|
}
|
|
|
|
// a typical frame consists of 8 packets. It can be enlonged by additional
|
|
// object frames
|
|
int palSize = colorPalette.Count;
|
|
|
|
var packetHeader = new byte[]
|
|
{
|
|
0x50, 0x47, // 0: "PG"
|
|
0x00, 0x00, 0x00, 0x00, // 2: PTS - presentation time stamp
|
|
0x00, 0x00, 0x00, 0x00, // 6: DTS - decoding time stamp
|
|
0x00, // 10: segment_type
|
|
0x00, 0x00 // 11: segment_length (bytes following till next PG)
|
|
};
|
|
var headerPcsStart = new byte[]
|
|
{
|
|
0x00, 0x00, 0x00, 0x00, // 0: video_width, video_height
|
|
0x10, // 4: hi nibble: frame_rate (0x10=24p), lo nibble: reserved
|
|
0x00, 0x00, // 5: composition_number (increased by start and end header)
|
|
0x80, // 7: composition_state (0x80: epoch start)
|
|
// 0x00: Normal
|
|
// 0x40: Acquisition Point
|
|
// 0x80: Epoch Start
|
|
0x00, // 8: palette_update_flag (0x80==true, 0x00==false), 7bit reserved
|
|
0x00, // 9: palette_id_ref (0..7)
|
|
0x01, // 10: number_of_composition_objects (0..2)
|
|
0x00, 0x00, // 11: 16bit object_id_ref
|
|
0x00, // 13: window_id_ref (0..1)
|
|
0x00, // 14: object_cropped_flag: 0x80, forced_on_flag = 0x040, 6bit reserved
|
|
0x00, 0x00, 0x00, 0x00 // 15: composition_object_horizontal_position, composition_object_vertical_position
|
|
};
|
|
var headerPcsEnd = new byte[]
|
|
{
|
|
0x00, 0x00, 0x00, 0x00, // 0: video_width, video_height
|
|
0x10, // 4: hi nibble: frame_rate (0x10=24p), lo nibble: reserved
|
|
0x00, 0x00, // 5: composition_number (increased by start and end header)
|
|
0x00, // 7: composition_state (0x00: normal)
|
|
0x00, // 8: palette_update_flag (0x80), 7bit reserved
|
|
0x00, // 9: palette_id_ref (0..7)
|
|
0x00 // 10: number_of_composition_objects (0..2)
|
|
};
|
|
var headerWds = new byte[]
|
|
{
|
|
0x01, // 0 : number of windows (currently assumed 1, 0..2 is legal)
|
|
0x00, // 1 : window id (0..1)
|
|
0x00, 0x00, 0x00, 0x00, // 2 : x-ofs, y-ofs
|
|
0x00, 0x00, 0x00, 0x00 // 6 : width, height
|
|
};
|
|
var headerOdsFirst = new byte[]
|
|
{
|
|
0x00, 0x00, // 0: object_id
|
|
0x00, // 2: object_version_number
|
|
0xC0, // 3: first_in_sequence (0x80), last_in_sequence (0x40), 6bits reserved
|
|
0x00, 0x00, 0x00, // 4: object_data_length - full RLE buffer length (including 4 bytes size info)
|
|
0x00, 0x00, 0x00, 0x00 // 7: object_width, object_height
|
|
};
|
|
var headerOdsNext = new byte[]
|
|
{
|
|
0x00, 0x00, // 0: object_id
|
|
0x00, // 2: object_version_number
|
|
0x40 // 3: first_in_sequence (0x80), last_in_sequence (0x40), 6bits reserved
|
|
};
|
|
|
|
int size = packetHeader.Length * (8 + numAddPackets);
|
|
size += headerPcsStart.Length + headerPcsEnd.Length;
|
|
size += 2 * headerWds.Length + headerOdsFirst.Length;
|
|
size += numAddPackets * headerOdsNext.Length;
|
|
size += (2 + palSize * 5) /* PDS */;
|
|
size += rleBuf.Length;
|
|
|
|
switch (alignment)
|
|
{
|
|
case ContentAlignment.BottomLeft:
|
|
pic.WindowXOffset = leftOrRightMargin;
|
|
pic.WindowYOffset = pic.Height - (bm.Height + bottomMargin);
|
|
break;
|
|
case ContentAlignment.BottomRight:
|
|
pic.WindowXOffset = pic.Width - bm.Width - bottomMargin;
|
|
pic.WindowYOffset = pic.Height - (bm.Height + leftOrRightMargin);
|
|
break;
|
|
case ContentAlignment.MiddleCenter:
|
|
pic.WindowXOffset = (pic.Width - bm.Width) / 2;
|
|
pic.WindowYOffset = (pic.Height - bm.Height) / 2;
|
|
break;
|
|
case ContentAlignment.MiddleLeft:
|
|
pic.WindowXOffset = leftOrRightMargin;
|
|
pic.WindowYOffset = (pic.Height - bm.Height) / 2;
|
|
break;
|
|
case ContentAlignment.MiddleRight:
|
|
pic.WindowXOffset = pic.Width - bm.Width - leftOrRightMargin;
|
|
pic.WindowYOffset = (pic.Height - bm.Height) / 2;
|
|
break;
|
|
case ContentAlignment.TopCenter:
|
|
pic.WindowXOffset = (pic.Width - bm.Width) / 2;
|
|
pic.WindowYOffset = bottomMargin;
|
|
break;
|
|
case ContentAlignment.TopLeft:
|
|
pic.WindowXOffset = leftOrRightMargin;
|
|
pic.WindowYOffset = bottomMargin;
|
|
break;
|
|
case ContentAlignment.TopRight:
|
|
pic.WindowXOffset = pic.Width - bm.Width - leftOrRightMargin;
|
|
pic.WindowYOffset = bottomMargin;
|
|
break;
|
|
default: // ContentAlignment.BottomCenter:
|
|
pic.WindowXOffset = (pic.Width - bm.Width) / 2;
|
|
pic.WindowYOffset = pic.Height - (bm.Height + bottomMargin);
|
|
break;
|
|
}
|
|
|
|
if (overridePosition != null &&
|
|
overridePosition.Value.X >= 0 && overridePosition.Value.X < bm.Width &&
|
|
overridePosition.Value.Y >= 0 && overridePosition.Value.Y < bm.Height)
|
|
{
|
|
pic.WindowXOffset = overridePosition.Value.X;
|
|
pic.WindowYOffset = overridePosition.Value.Y;
|
|
}
|
|
|
|
int yOfs = pic.WindowYOffset - Core.CropOfsY;
|
|
if (yOfs < 0)
|
|
{
|
|
yOfs = 0;
|
|
}
|
|
else
|
|
{
|
|
int yMax = pic.Height - pic.WindowHeight - 2 * Core.CropOfsY;
|
|
if (yOfs > yMax)
|
|
{
|
|
yOfs = yMax;
|
|
}
|
|
}
|
|
|
|
int h = pic.Height - 2 * Core.CropOfsY;
|
|
|
|
var buf = new byte[size];
|
|
int index = 0;
|
|
|
|
int fpsId = GetFpsId(fps);
|
|
|
|
/* time (in 90kHz resolution) needed to initialize (clear) the screen buffer
|
|
based on the composition pixel rate of 256e6 bit/s - always rounded up */
|
|
int frameInitTime = (pic.Width * pic.Height * 9 + 3199) / 3200; // better use default height here
|
|
/* time (in 90kHz resolution) needed to initialize (clear) the window area
|
|
based on the composition pixel rate of 256e6 bit/s - always rounded up
|
|
Note: no cropping etc. -> window size == image size */
|
|
int windowInitTime = (bm.Width * bm.Height * 9 + 3199) / 3200;
|
|
/* time (in 90kHz resolution) needed to decode the image
|
|
based on the decoding pixel rate of 128e6 bit/s - always rounded up */
|
|
int imageDecodeTime = (bm.Width * bm.Height * 9 + 1599) / 1600;
|
|
// write PCS start - Presentation Composition Segment (also called the Control Segment)
|
|
packetHeader[10] = 0x16; // ID
|
|
int dts = pic.StartTimeForWrite - (frameInitTime + windowInitTime + imageDecodeTime); //int dts = pic.StartTimeForWrite - windowInitTime; ???
|
|
|
|
ToolBox.SetDWord(packetHeader, 2, pic.StartTimeForWrite); // PTS
|
|
ToolBox.SetDWord(packetHeader, 6, dts); // DTS
|
|
ToolBox.SetWord(packetHeader, 11, headerPcsStart.Length); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
ToolBox.SetWord(headerPcsStart, 0, pic.Width);
|
|
ToolBox.SetWord(headerPcsStart, 2, h); // cropped height
|
|
ToolBox.SetByte(headerPcsStart, 4, fpsId);
|
|
ToolBox.SetWord(headerPcsStart, 5, pic.CompositionNumber);
|
|
headerPcsStart[14] = (byte)(pic.IsForced ? 0x40 : 0);
|
|
ToolBox.SetWord(headerPcsStart, 15, pic.WindowXOffset);
|
|
ToolBox.SetWord(headerPcsStart, 17, yOfs);
|
|
for (int i = 0; i < headerPcsStart.Length; i++)
|
|
{
|
|
buf[index++] = headerPcsStart[i];
|
|
}
|
|
|
|
// write WDS
|
|
packetHeader[10] = 0x17; // ID
|
|
int timestamp = pic.StartTimeForWrite - windowInitTime;
|
|
ToolBox.SetDWord(packetHeader, 2, timestamp); // PTS (keep DTS)
|
|
ToolBox.SetWord(packetHeader, 11, headerWds.Length); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
ToolBox.SetWord(headerWds, 2, pic.WindowXOffset);
|
|
ToolBox.SetWord(headerWds, 4, yOfs);
|
|
ToolBox.SetWord(headerWds, 6, bm.Width);
|
|
ToolBox.SetWord(headerWds, 8, bm.Height);
|
|
for (int i = 0; i < headerWds.Length; i++)
|
|
{
|
|
buf[index++] = headerWds[i];
|
|
}
|
|
|
|
// write PDS - Palette Definition Segment
|
|
packetHeader[10] = 0x14; // ID
|
|
ToolBox.SetDWord(packetHeader, 2, dts); // PTS (=DTS of PCS/WDS)
|
|
ToolBox.SetDWord(packetHeader, 6, 0); // DTS (0)
|
|
ToolBox.SetWord(packetHeader, 11, 2 + palSize * 5); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
buf[index++] = 0;
|
|
buf[index++] = 0;
|
|
for (int i = 0; i < palSize; i++)
|
|
{
|
|
buf[index++] = (byte)i; // index
|
|
buf[index++] = pal.GetY()[i]; // Y
|
|
buf[index++] = pal.GetCr()[i]; // Cr
|
|
buf[index++] = pal.GetCb()[i]; // Cb
|
|
buf[index++] = pal.GetAlpha()[i]; // Alpha
|
|
}
|
|
|
|
// write first OBJ
|
|
int bufSize = rleBuf.Length;
|
|
int rleIndex = 0;
|
|
if (bufSize > 0xffe4)
|
|
{
|
|
bufSize = 0xffe4;
|
|
}
|
|
|
|
packetHeader[10] = 0x15; // ID
|
|
timestamp = 0;
|
|
ToolBox.SetDWord(packetHeader, 2, timestamp); // PTS
|
|
ToolBox.SetDWord(packetHeader, 6, dts); // DTS
|
|
ToolBox.SetWord(packetHeader, 11, headerOdsFirst.Length + bufSize); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
int marker = (int)((numAddPackets == 0) ? 0xC0000000 : 0x80000000);
|
|
ToolBox.SetDWord(headerOdsFirst, 3, marker | (rleBuf.Length + 4));
|
|
ToolBox.SetWord(headerOdsFirst, 7, bm.Width);
|
|
ToolBox.SetWord(headerOdsFirst, 9, bm.Height);
|
|
for (int i = 0; i < headerOdsFirst.Length; i++)
|
|
{
|
|
buf[index++] = headerOdsFirst[i];
|
|
}
|
|
|
|
for (int i = 0; i < bufSize; i++)
|
|
{
|
|
buf[index++] = rleBuf[rleIndex++];
|
|
}
|
|
|
|
// write additional OBJ packets
|
|
bufSize = rleBuf.Length - bufSize; // remaining bytes to write
|
|
for (int p = 0; p < numAddPackets; p++)
|
|
{
|
|
int psize = bufSize;
|
|
if (psize > 0xffeb)
|
|
{
|
|
psize = 0xffeb;
|
|
}
|
|
|
|
packetHeader[10] = 0x15; // ID (keep DTS & PTS)
|
|
ToolBox.SetWord(packetHeader, 11, headerOdsNext.Length + psize); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
for (int i = 0; i < headerOdsNext.Length; i++)
|
|
{
|
|
buf[index++] = headerOdsNext[i];
|
|
}
|
|
|
|
for (int i = 0; i < psize; i++)
|
|
{
|
|
buf[index++] = rleBuf[rleIndex++];
|
|
}
|
|
|
|
bufSize -= psize;
|
|
}
|
|
|
|
// write END
|
|
packetHeader[10] = 0x80; // ID
|
|
ToolBox.SetDWord(packetHeader, 6, 0); // DTS (0) (keep PTS of ODS)
|
|
ToolBox.SetWord(packetHeader, 11, 0); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
// write PCS end
|
|
packetHeader[10] = 0x16; // ID
|
|
ToolBox.SetDWord(packetHeader, 2, pic.EndTimeForWrite); // PTS
|
|
dts = pic.EndTimeForWrite - 90;
|
|
ToolBox.SetDWord(packetHeader, 6, dts); // DTS
|
|
ToolBox.SetWord(packetHeader, 11, headerPcsEnd.Length); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
ToolBox.SetWord(headerPcsEnd, 0, pic.Width);
|
|
ToolBox.SetWord(headerPcsEnd, 2, h); // cropped height
|
|
ToolBox.SetByte(headerPcsEnd, 4, fpsId);
|
|
ToolBox.SetWord(headerPcsEnd, 5, pic.CompositionNumber + 1);
|
|
for (int i = 0; i < headerPcsEnd.Length; i++)
|
|
{
|
|
buf[index++] = headerPcsEnd[i];
|
|
}
|
|
|
|
// write WDS - Window Definition Segment
|
|
packetHeader[10] = 0x17; // ID
|
|
timestamp = pic.EndTimeForWrite - windowInitTime;
|
|
ToolBox.SetDWord(packetHeader, 2, timestamp); // PTS
|
|
ToolBox.SetDWord(packetHeader, 6, dts - windowInitTime); // DTS
|
|
ToolBox.SetWord(packetHeader, 11, headerWds.Length); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
ToolBox.SetWord(headerWds, 2, pic.WindowXOffset);
|
|
ToolBox.SetWord(headerWds, 4, yOfs);
|
|
ToolBox.SetWord(headerWds, 6, bm.Width);
|
|
ToolBox.SetWord(headerWds, 8, bm.Height);
|
|
for (int i = 0; i < headerWds.Length; i++)
|
|
{
|
|
buf[index++] = headerWds[i];
|
|
}
|
|
|
|
// write END
|
|
packetHeader[10] = 0x80; // ID
|
|
ToolBox.SetDWord(packetHeader, 2, dts); // PTS (DTS of end PCS)
|
|
ToolBox.SetDWord(packetHeader, 6, 0); // DTS (0)
|
|
ToolBox.SetWord(packetHeader, 11, 0); // size
|
|
for (int i = 0; i < packetHeader.Length; i++)
|
|
{
|
|
buf[index++] = packetHeader[i];
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
}
|
|
}
|