This is the first time I am asking a question here and I couldn't find anything in the search function.
The Problem:
I am receiving (via HttpClient
) an IInputStream (MP4 Segments). The Problem is, that my MediaPlayer doesn't play it, because this MP4 segments contain a single Atom which causes an ERR_FILETYPE_NOT_SUPPORTED
error.
So if I manually remove this Atom ( UUID | Position: moov -> trak -> mdia -> minf -> stbl -> stsd -> encv -> sinf -> schi -> uui
) it works perfectly fine.
Problem is that, as I have an adaptive stream, I have to modify those segments (about 1250 per movie) on the fly before sending it into the AdaptiveMediaSource -- and I cannot get this to work properly.
Code:
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using static System.Diagnostics.Debug;
namespace BlackMagic
{
public class EvilVoodooClass
{
private static readonly bool _commitFlag = false;
/// <summary>
/// MP4 File parser
/// </summary>
/// <param name="awsInputStream"></param>
public static async Task<Stream> ParseFile(IInputStream awsInputStream)
{
int moovPos = 0, moovSize = 0;
int trakPos = 0, trakSize = 0;
int mdiaPos = 0, mdiaSize = 0;
int minfPos = 0, minfSize = 0;
int stblPos = 0, stblSize = 0;
int stsdPos = 0, stsdSize = 0;
int encvPos = 0, encvSize = 0;
int sinfPos = 0, sinfSize = 0;
int schiPos = 0, schiSize = 0;
int uuidPos = 0, uuidSize = 0;
var bufferSize = new Windows.Storage.Streams.Buffer(200000);
var readBuffer = await awsInputStream.ReadAsync(bufferSize, 200000, InputStreamOptions.ReadAhead);
var tempInputStream = readBuffer.AsStream();
var tempOutStream = readBuffer.AsStream();
WriteLine("Parsing segment... ");
using (var br = new BinaryReader(tempInputStream))
{
var originalFilelength = (int)br.BaseStream.Length;
WriteLine($"original File length: {originalFilelength}");
br.BaseStream.Seek(4, SeekOrigin.Begin);
if ("ftypiso6" == Encoding.ASCII.GetString(br.ReadBytes(8)))
{
FindAtom(br, 0, "moov", ref moovPos, ref moovSize);
WriteLine($"found moov at position: {moovPos} / size: {moovSize}");
FindAtom(br, moovPos + 8, "trak", ref trakPos, ref trakSize);
WriteLine($"found trak at position: {trakPos} / size: {trakSize}");
FindAtom(br, trakPos + 8, "mdia", ref mdiaPos, ref mdiaSize);
WriteLine($"found mdiaPos at position: {mdiaPos} / size: {mdiaSize}");
FindAtom(br, moovPos + 8, "minf", ref minfPos, ref minfSize);
WriteLine($"found minfPos at position: {minfPos} / size: {minfSize}");
FindAtom(br, minfPos + 8, "stbl", ref stblPos, ref stblSize);
WriteLine($"found stblPos at position: {stblPos} / size: {stblSize}");
FindAtom(br, stblPos + 8, "stsd", ref stsdPos, ref stsdSize);
WriteLine($"found stsdSize at position: {stsdPos} / size: {stsdSize}");
// Breakpoint
FindAtom(br, stsdPos + 8, "encv", ref encvPos, ref encvSize);
WriteLine($"found encvSize at position: {encvPos} / size: {encvSize}");
FindAtom(br, encvPos + 8, "sinf", ref sinfPos, ref sinfSize);
WriteLine($"found sinfPos at position: {sinfPos} / size: {sinfSize}");
FindAtom(br, sinfPos + 8, "schi", ref schiPos, ref schiSize);
WriteLine($"found schiSize at position: {schiPos} / size: {schiSize}");
FindAtom(br, schiPos, "uuid", ref uuidPos, ref uuidSize);
WriteLine($"found UU--ID at position: {uuidPos} / size: {uuidSize}");
br.BaseStream.Seek(0, SeekOrigin.Begin);
if (uuidPos == 0)
{
WriteLine(" [No UUID Atom found]");
return tempOutStream;
}
WriteLine("[UUID Atom found]");
if (!_commitFlag) return tempOutStream;
WriteLine("Rewriting segments... ");
try
{
br.BaseStream.Seek(moovPos, SeekOrigin.Begin);
var uuidBuffer = br.ReadBytes(moovSize);
if (BufferArrayFindTag(ref uuidBuffer, "uuid", ref uuidPos, ref uuidSize))
CleanUuid(ref uuidBuffer, uuidPos, uuidSize);
UpdateUuidSize(ref uuidBuffer);
using (var binWri = new BinaryWriter(tempOutStream))
{
br.BaseStream.Seek(0, SeekOrigin.Begin);
BufferedBinaryCopy(br, binWri, moovPos);
if (uuidBuffer.Length > 8) binWri.Write(uuidBuffer);
br.BaseStream.Seek(moovSize, SeekOrigin.Current);
BufferedBinaryCopy(br, binWri, originalFilelength - (moovPos + moovSize));
}
WriteLine("FIXED!");
return tempOutStream;
}
catch
{
WriteLine(" --FAILED--");
return tempOutStream;
}
}
else
{
WriteLine("[No MP4 Segment] ");
return tempOutStream;
}
}
}
public static bool BufferedBinaryCopy(BinaryReader source, BinaryWriter destination, int length)
{
const int iobufferSize = 32 * 1024 * 1024;
var bytesLeft = length;
while (bytesLeft > 0)
{
var bytesToRead = Math.Min(iobufferSize, bytesLeft);
var buffer = source.ReadBytes(bytesToRead);
bytesLeft -= buffer.Length;
destination.Write(buffer);
}
return true;
}
public static bool FindAtom(BinaryReader br, int offset, string tag, ref int pos, ref int size)
{
try
{
br.BaseStream.Seek(offset, SeekOrigin.Begin);
while (br.BaseStream.Position < br.BaseStream.Length - 8)
{
var tagdata = br.ReadBytes(4);
Array.Reverse(tagdata);
var tagsize = (int)BitConverter.ToUInt32(tagdata, 0);
if (tagsize == 1)
{
var exdata = br.ReadBytes(4);
Array.Reverse(exdata);
tagsize = (tagsize << 32) + (int)BitConverter.ToUInt32(exdata, 0);
}
tagdata = br.ReadBytes(4);
var tagname = Encoding.ASCII.GetString(tagdata);
if (tagname == tag)
{
pos = (int)br.BaseStream.Position - 8;
size = tagsize;
return true;
}
if (tagsize == 0)
return false;
br.BaseStream.Seek(tagsize - 8, SeekOrigin.Current);
}
}
catch (Exception ex)
{
WriteLine(ex.ToString());
}
return false;
}
public static void CleanUuid(ref byte[] buffer, int pos, int size)
{
var lb = buffer.ToList();
lb.RemoveRange(pos, size);
buffer = lb.ToArray();
}
public static void UpdateUuidSize(ref byte[] buffer)
{
var bufferLength = buffer.Length;
var tagsize = BitConverter.GetBytes(bufferLength);
Array.Reverse(tagsize);
var lb = buffer.ToList();
lb.RemoveRange(0, 4);
lb.InsertRange(0, tagsize);
buffer = lb.ToArray();
}
public static bool RewriteFile(ref BinaryReader input, long udtaPos, long udtaSize, long metaPos, long metaSize,
long xtraPos, long xtraSize)
{
var tempname = string.Format(@"{0}.txt", Guid.NewGuid());
using (var b = new BinaryWriter(File.Open("tempname", FileMode.Create, FileAccess.Read)))
{
//throw new NotImplementedException();
}
return true;
}
public static bool BufferArrayFindTag(ref byte[] bb, string tag, ref int tagPos, ref int tagSize)
{
var pattern = Encoding.UTF8.GetBytes(tag);
var byteIndex = BufferArrayIndexOf(bb, pattern);
if (byteIndex < 0) return false;
tagPos = byteIndex - 4;
tagSize = (bb[byteIndex - 4] << 24) | (bb[byteIndex - 3] << 16) | (bb[byteIndex - 2] << 8) | bb[byteIndex - 1];
return true;
}
public static int BufferArrayIndexOf(byte[] data, byte[] pattern)
{
if (pattern.Length > data.Length) return -1;
for (var i = 0; i < data.Length - pattern.Length; i++)
{
var found = !pattern.Where((t, j) => data[i + j] != t).Any();
if (found) return i;
}
return -1;
}
}
}
hm... How could I solve this problem? any clues?
I think you can implement a Stream class yourself. When somebody reads your stream, you read the original IInputStream and manipulate it and return the result to the caller.
By the way, if you add "using System.IO", it will be very easy to convert .NET Streams to or from Windows Streams.