How to convert base64 mpeg to AudioClip in Unity? - c#

After some research, I have got the following
private float[] Normalize(float[] data) {
float max = float.MinValue;
for (int i = 0; i < data.Length; i++){
if (System.Math.Abs(data[i]) > max) max = System.Math.Abs(data[i]);
}
for (int i = 0; i < data.Length; i++) data[i] = data[i] / max;
return data;
}
private float[] ConvertByteToFloat(byte[] array){
float[] floatArr = new float[array.Length / 4];
for (int i = 0; i < floatArr.Length; i++){
if (System.BitConverter.IsLittleEndian) System.Array.Reverse(array, i * 4, 4);
floatArr[i] = System.BitConverter.ToSingle(array, i * 4) ;
}
return Normalize(floatArr);
}
byte[] bytes = System.Convert.FromBase64String(data);
float[] f = ConvertByteToFloat(bytes);
qa[i] = AudioClip.Create("qjAudio", f.Length, 2, 44100, false);
qa[i].SetData(f,0);
However, all I heard was some random noise.
How can I correctly convert a base64 mp3 string into AudioClip?

I have done some reseach and come up that UnityWebRequestMultimedia.GetAudioClip is way to go.
Convert your base64 string into byte array
var audioBytes = Convert.FromBase64String(base64EncodedMp3String);
Create temp path to store it as reference
var tempPath = Application.persistentDataPath + "tmpMP3Base64.mp3";
Write all bytes to that path
File.WriteAllBytes(tempPath, audioBytes);
Use UnityWebRequestMultimedia.GetAudioClip to read from that path
UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(tempPath, AudioType.MPEG);
Here is a complete example:
[SerializeField] private AudioSource _audioSource;
private void Start()
{
StartCoroutine(ConvertBase64ToAudioClip(EXAMPLE_BASE64_MP3_STRING, _audioSource));
}
IEnumerator ConvertBase64ToAudioClip(string base64EncodedMp3String, AudioSource audioSource)
{
var audioBytes = Convert.FromBase64String(base64EncodedMp3String);
var tempPath = Application.persistentDataPath + "tmpMP3Base64.mp3";
File.WriteAllBytes(tempPath, audioBytes);
UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(tempPath, AudioType.MPEG);
yield return request.SendWebRequest();
if (request.result.Equals(UnityWebRequest.Result.ConnectionError))
Debug.LogError(request.error);
else
{
audioSource.clip = DownloadHandlerAudioClip.GetContent(request);
audioSource.Play();
}
File.Delete(tempPath);
}

Related

How to convert 16Bit byte array to audio clip data correctly?

I work with Media Foundataion and what I need to do is convert sound sample frame from byte to audio float data. In order to do it I use such method (that I found somewhere at google):
private static float[] Convert16BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize)
{
int wavSize = BitConverter.ToInt32(source, headerOffset);
headerOffset += sizeof(int);
Debug.AssertFormat(wavSize > 0 && wavSize == dataSize, "Failed to get valid 16-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, headerOffset);
int x = sizeof(Int16); // block size = 2
int convertedSize = wavSize / x;
float[] data = new float[convertedSize];
Int16 maxValue = Int16.MaxValue;
int i = 0;
while (i < convertedSize)
{
int offset = i * x + headerOffset;
data[i] = (float)BitConverter.ToInt16(source, offset) / maxValue;
++i;
}
Debug.AssertFormat(data.Length == convertedSize, "AudioClip .wav data is wrong size: {0} == {1}", data.Length, convertedSize);
return data;
}
I use it like this :
...
byte[] source = ...; // lenght 43776
... = Convert16BitByteArrayToAudioClipData(source , 0, 0);
...
Looks like this method works wrong, because if I pass an array with size 43776 as a result in while loop at index i = 21886 offset value will be offset = 43776 it lead to exception at this next method
data[i] = (float)BitConverter.ToInt16(source /*43776*/, offset /*43776*/) / maxValue;
because this values could not be the same.
Question is - how to fix this method? Or maybe someone can advice what to use instead?
EDIT
private static float[] Convert16BitByteArrayToAudioClipData(byte[] source)
{
float[] data = new float[source.Length];
for (int i = 0; i < source.Length; i++)
{
data[i] = (float) source[i];
}
return data;
}
Integers need to become -1..+1 floating point values
private static float[] Convert16BitByteArrayToAudioClipData(byte[] source)
{
float[] data = new float[source.Length];
for (int i = 0; i < source.Length; i++)
{
data[i] = ((float) source[i] / Int16.MaxValue); // <<---
}
return data;
}
Eventually I did it this way:
public static float[] Convert16BitByteArrayToAudioClipData(byte[] source)
{
int x = sizeof(Int16);
int convertedSize = source.Length / x;
float[] data = new float[convertedSize];
Int16 maxValue = Int16.MaxValue;
for (int i = 0; i < convertedSize; i++)
{
int offset = i * x;
data[i] = (float)BitConverter.ToInt16(source, offset) / maxValue;
++i;
}
return data;
}

opus and NAudio streaming out of sync

I am adding voip in the game and since Unity's Microphone class is not supported in Web_GL and is already slow and gives floats instead of bytes. Now some people suggested me to use codec i.e Opus and then I found its wrapper along with its demo which used NAudio, well I was fairly happy with it, it was using some extra loops which after removing also gave the same result but anyway it also gave 4000 bytes with 48k sample rate which I reduced to 8k and max buffer size to 350. Here's the code for that script
private void Start()
{
//StartEncoding();
UnityEditor.EditorApplication.playmodeStateChanged = PlayModeStateChangedHandler;
}
private void PlayModeStateChangedHandler()
{
if (UnityEditor.EditorApplication.isPaused)
{
StopEncoding();
}
}
public void StartGame()
{
StartEncoding();
}
private void StartEncoding()
{
_client = FindObjectOfType<Client>();
_client.AudioReceivers += UpdateAudioOutput;
_startTime = DateTime.Now;
_bytesSent = 0;
_segmentFrames = 160;
_encoder = OpusEncoder.Create(8000, 1, FragLabs.Audio.Codecs.Opus.Application.Voip);
_encoder.MaxDataBytes = 350;
_encoder.Bitrate = 4000;
_decoder = OpusDecoder.Create(8000, 1);
_decoder.MaxDataBytes = 175;
_bytesPerSegment = _encoder.FrameByteCount(_segmentFrames);
_waveIn = new WaveIn(WaveCallbackInfo.FunctionCallback());
_waveIn.BufferMilliseconds = 50;
_waveIn.DeviceNumber = 0;
_waveIn.DataAvailable += _waveIn_DataAvailable;
_waveIn.WaveFormat = new WaveFormat(8000, 16, 1);
_playBuffer = new BufferedWaveProvider(new WaveFormat(8000, 16, 1));
_playBuffer.DiscardOnBufferOverflow = true;
_waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
_waveOut.DeviceNumber = 0;
_waveOut.Init(_playBuffer);
_waveOut.Play();
_waveIn.StartRecording();
if (_timer == null)
{
_timer = new Timer();
_timer.Interval = 1000;
_timer.Elapsed += _timer_Tick;
}
_timer.Start();
}
private void _timer_Tick(object sender, EventArgs e)
{
var timeDiff = DateTime.Now - _startTime;
var bytesPerSecond = _bytesSent / timeDiff.TotalSeconds;
}
byte[] _notEncodedBuffer = new byte[0];
private void _waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
byte[] soundBuffer = new byte[e.BytesRecorded + _notEncodedBuffer.Length];
for (int i = 0; i < _notEncodedBuffer.Length; i++)
soundBuffer[i] = _notEncodedBuffer[i];
for (int i = 0; i < e.BytesRecorded; i++)
soundBuffer[i + _notEncodedBuffer.Length] = e.Buffer[i];
int byteCap = _bytesPerSegment;
int segmentCount = (int)Math.Floor((decimal)soundBuffer.Length / byteCap);
int segmentsEnd = segmentCount * byteCap;
int notEncodedCount = soundBuffer.Length - segmentsEnd;
_notEncodedBuffer = new byte[notEncodedCount];
for (int i = 0; i < notEncodedCount; i++)
{
_notEncodedBuffer[i] = soundBuffer[segmentsEnd + i];
}
for (int i = 0; i < segmentCount; i++)
{
byte[] segment = new byte[byteCap];
for (int j = 0; j < segment.Length; j++)
segment[j] = soundBuffer[(i * byteCap) + j];
int len;
byte[] buff = _encoder.Encode(segment, segment.Length, out len);
SendToServer(buff, len);
}
}
public void UpdateAudioOutput(byte[] ba, int len)
{
int outlen = len;
byte[] buff = new byte[len];
buff = _decoder.Decode(ba, outlen, out outlen);
_playBuffer.AddSamples(buff, 0, outlen);
}
private void SendToServer(byte[] EncodedAudio, int Length)
{
print("SENDING AUDIO");
//print("audio length : " + EncodedAudio.Length);
_client.Send(EncodedAudio, Length);
//UpdateAudioOutput(EncodedAudio, Length);
}
private void StopEncoding()
{
_timer.Stop();
_waveIn.StopRecording();
_waveIn.Dispose();
_waveIn = null;
_waveOut.Stop();
_waveOut.Dispose();
_waveOut = null;
_playBuffer = null;
_encoder.Dispose();
_encoder = null;
_decoder.Dispose();
_decoder = null;
}
private void OnApplicationQuit()
{
StopEncoding();
}
Now here is the tcp send and receive, they are pretty much same for the client and the server
public void Send(byte[] data, int customParamLen = 0)
{
if (!socketReady)
{
return;
}
byte messageType = (1 << 3); // assume that 0000 1000 would be the Message type
byte[] message = data;
byte[] length = BitConverter.GetBytes(message.Length);
byte[] customParam = BitConverter.GetBytes(customParamLen); //length also 4/sizeof(int)
byte[] buffer = new byte[sizeof(int) + message.Length + 1 + customParam.Length];
buffer[0] = messageType;
//Enter length in the buffer
for (int i = 0; i < sizeof(int); i++)
{
buffer[i + 1] = length[i];
}
//Enter data in the buffer
for (int i = 0; i < message.Length; i++)
{
buffer[i + 1 + sizeof(int)] = message[i];
}
//Enter custom Param in the buffer
for (int i = 0; i < sizeof(int); i++)
{
buffer[i + 1 + sizeof(int) + message.Length] = customParam[i];
}
heavyStream.Write(buffer, 0, buffer.Length);
print("Writtin bytes");
}
if (heavyStream.DataAvailable)
{
print("Data Receiving YAY!");
//Get message Type
byte messageType = (byte)heavyStream.ReadByte();
//Get length of the Data
byte[] lengthBuffer = new byte[sizeof(int)];
int recv = heavyStream.Read(lengthBuffer, 0, lengthBuffer.Length);
if (recv == sizeof(int))
{
int messageLen = BitConverter.ToInt32(lengthBuffer, 0);
//Get the Data
byte[] messageBuffer = new byte[messageLen];
recv = heavyStream.Read(messageBuffer, 0, messageBuffer.Length);
if (recv == messageLen)
{
// messageBuffer contains the whole message ...
//Get length paramater needed for opus to decode
byte[] customParamAudioLen = new byte[sizeof(int)];
recv = heavyStream.Read(customParamAudioLen, 0, customParamAudioLen.Length);
if (recv == sizeof(int))
{
AudioReceivers(messageBuffer, BitConverter.ToInt32(customParamAudioLen, 0) - 5);
print("Done! Everything went straight as planned");
}
}
}
Now the problem is that the audio is choppy and has gaps in them, as the time flies the more out of sync it becomes.
UPDATE
Still not fixed.
It looks like you're just sending audio straight out with no jitter buffer on the receiving end. This means if you have any variability in latency you'll start to hear gaps.
What you need to do is buffer audio on the client side - until you have a good amount, say 400ms, then start playing. That gives you a buffer of extra time to account for jitter.
This is a very naive approach, but gives you something to play with - you'll probably want to look at adaptive jitter buffers, and probably switch to UDP instead of TCP to get better performance. With UDP you will need to deal with lost packets, out of order etc.
Have a look at Speex which has a Jitter Buffer https://github.com/xiph/speex or Mumble which uses Speex for VOIP https://github.com/mumble-voip/mumble

Unsupported PixelFormat - SetPixelData

I have this function which converts the array of ushort to a grayscale image. I do the converts to convert the values to a byte-array and then use BitmapEncoder.
public static async Task<StorageFile> WriteableBitmapToStorageFile(ushort[,] image, bool isScaleValues, List<KeyValuePair<string, BitmapTypedValue>> metadata)
{
//Setup image
var imgHeight = image.GetLength(0);
var imgWidth = image.GetLength(1);
float maxVal = 1;
if (isScaleValues)
{
for (int i = 0; i < imgHeight; i++)
{
for (int j = 0; j < imgWidth; j++)
{
if (maxVal < image[i, j])
{
maxVal = image[i, j];
}
}
}
}
byte[] data = new byte[imgWidth * imgHeight];
if (image != null)
{
if (isScaleValues)
{
for (int x = 0; x < imgHeight; x++)
for (int y = 0; y < imgWidth; y++)
data[x * imgWidth + y] = (byte)(((double)UInt16.MaxValue * (double)image[x, y]) / (double)maxVal);
}
else
{
for (int x = 0; x < imgHeight; x++)
for (int y = 0; y < imgWidth; y++)
data[x * imgWidth + y] = (byte)image[x, y];
}
}
string FileName = "MyFile.png";
var file =
await
Windows.Storage.ApplicationData.Current.TemporaryFolder.CreateFileAsync(FileName,
CreationCollisionOption.GenerateUniqueName);
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Gray16, BitmapAlphaMode.Ignore,
(uint) imgWidth,
(uint) imgHeight,
2822.222222222222,
2822.222222222222,
data);
await encoder.BitmapProperties.SetPropertiesAsync(metadata);
await encoder.FlushAsync();
}
return file;
}
I get this exception at SetPixelData:
An exception of type 'System.ArgumentException' occurred in mscorlib.ni.dll but was not handled in user code
WinRT information: Windows.Graphics.Imaging: The bitmap pixel format is unsupported.
Additional information: The parameter is incorrect.
Windows.Graphics.Imaging: The bitmap pixel format is unsupported.
The 2D ushort array is already 16-bit grayscale image with first dimension being the height and the 2nd being the width. Since grayscale is apparently not supported, I will need to save it as Rgba16, so the question is: How to convert grayscale to RBG?
AFAIK, I just need to set all R,G,B to the same value but how would I place the values in the array.
What is the RGBA16 format in binary?
Since you have had a 2D ushort array contains 16 bpp grayscale pixels. I'd suggest you use this array to create a SoftwareBitmap with Gray16 pixel format first and then use SoftwareBitmap.Convert method to convert it to Rgba16 pixel format.
BitmapPixelFormat.Gray16 represent 16 bpp grayscale pixel format which means each pixel takes two bytes. So we need a byte array whose length is twice of the ushort array and use BitConverter.GetBytes(UInt16) method to convert ushort to byte[]. Following is a simple sample:
ushort[,] image = { { ushort.MinValue, ushort.MaxValue, ushort.MaxValue }, { ushort.MaxValue, ushort.MinValue, ushort.MaxValue }, { ushort.MaxValue, ushort.MaxValue, ushort.MinValue } };
var imgHeight = image.GetLength(0);
var imgWidth = image.GetLength(1);
byte[] data = new byte[image.Length * 2];
for (int i = 0; i < imgHeight; i++)
{
for (int j = 0; j < imgWidth; j++)
{
byte[] byteArray = BitConverter.GetBytes(image[i, j]);
data[i * imgWidth * 2 + j * 2] = byteArray[0];
data[i * imgWidth * 2 + j * 2 + 1] = byteArray[1];
}
}
//Create a SoftwareBitmap with 16 bpp grayscale pixel format
var softwareBitmap = new SoftwareBitmap(BitmapPixelFormat.Gray16, imgWidth, imgHeight);
softwareBitmap.CopyFromBuffer(data.AsBuffer());
//Convert pixel format to Rgba16 so that we can save it to the file
softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Rgba16);
var outputFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync("MyFile.png", CreationCollisionOption.GenerateUniqueName);
//Save softwareBitmap to file
using (IRandomAccessStream stream = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
{
// Create an encoder with the desired format
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
// Set the software bitmap
encoder.SetSoftwareBitmap(softwareBitmap);
await encoder.FlushAsync();
}

Neural Net - Feed Forward, Matrix Multiplication in C#

I'm attempting to make a Neural Network in C#, I based the design in a python code I made a while back. But somehow the end result is not the same.
I'm new to C# and I'm using it in Unity, so I have limitation to library uses.
In python numpy can do matrix multiplications with the numpy.dot() method. I Haven't found something similar in C#, especially in Unity. So I had to do it by hand.
The Python code:
import numpy as np
class NN:
def __init__(self, n_input, n_hidden_layers, n_hidden_nodes, n_output):
self.weights_hidden = []
for n in range(n_hidden_layers + 1):
if n == 0:
size = n_input, n_hidden_nodes
elif n == n_hidden_layers:
size = n_hidden_nodes, n_output
else:
size = n_hidden_nodes, n_hidden_nodes
self.weights_hidden.append(
np.random.random(size)
)
#staticmethod
def activation(x):
return np.tanh(x)
def feed_forward(self, ip):
input_values = (ip - np.mean(ip, axis=0)) / np.std(ip, axis=0)
for w, weights in enumerate(self.weights_hidden):
if w == 0:
result = input_values
result = np.array(
map(self.activation, result.dot(weights))
)
return result
ANN = NN(n_input=5, n_hidden_layers=2, n_hidden_nodes=3, n_output=1)
print ANN.feed_forward([1, 2, 3, 4, 5])
My attempt to convert it to C#.
using UnityEngine;
using System.Collections;
public class neural_net : MonoBehaviour {
int n_inputs;
int n_hidden_layers;
int n_hidden_nodes;
int n_outputs;
float[] inputs;
ArrayList hidden_weights;
ArrayList hidden_results;
float[] output_results;
public void init(int n_inputs, int n_hidden_layers, int n_hidden_nodes, int n_outputs){
this.n_inputs = n_inputs;
this.n_hidden_layers = n_hidden_layers;
this.n_hidden_nodes = n_hidden_nodes;
this.n_outputs = n_outputs;
this.hidden_weights = new ArrayList ();
this.hidden_results = new ArrayList ();
this.output_results = new float[n_outputs];
int rows;
int columns;
for (int h = 0; h < n_hidden_layers + 2; h++) {
if (h == 0){
// input -> hidden
rows = n_inputs;
columns = n_hidden_nodes;
}
else if(h == n_hidden_layers + 1){
// hidden -> output
rows = n_hidden_nodes;
columns = n_outputs;
}
else {
// hidden -> hidden
rows = n_hidden_nodes;
columns = n_hidden_nodes;
}
float[] hidden_result = new float[rows*columns];
hidden_results.Add(hidden_results);
float[,] target = new float[rows,columns];
string test = "";
for(int r = 0; r < rows; r++){
for(int c = 0; c < columns; c++){
target[r,c] = Random.Range(0.0f, 1.0f);
test += target[r,c] + ", ";
}
}
hidden_weights.Add(target);
}
}
float activation(float x){
// tanh(x);
return (1 - Mathf.Exp (-2 * x)) / (1 + Mathf.Exp (-2 * x));
}
float[] _dot_matrix(float[] results, float[,] weights){
float[] new_matrix = new float[weights.GetLength(1)];
string t0 = "";
for (int r = 0; r < weights.GetLength(1); r++){
float res = 0;
for (int c = 0; c < weights.GetLength(0); c++) {
res += results[c] * weights[c,r];
}
new_matrix[r] = res;
}
return new_matrix;
}
float[] _map_activation(float[] pre_results){
float[] results = new float[pre_results.Length];
for (int i = 0; i < results.Length; i++) {
results[i] = activation(pre_results[i]);
}
return results;
}
float[] feed_forward(){
int h;
for (h = 0; h < n_hidden_layers + 2; h++) {
float[] dot_matrix_result;
if(h == 0){
dot_matrix_result = _dot_matrix(inputs, (float[,])hidden_weights[h]);
}
else if (h == n_hidden_layers +1){
dot_matrix_result = _dot_matrix((float[])hidden_results[h-1], (float[,])hidden_weights[h]);
output_results = _map_activation(dot_matrix_result);
break;
}
else {
dot_matrix_result = _dot_matrix((float[])hidden_results[h-1], (float[,])hidden_weights[h]);
}
float[] result = _map_activation(dot_matrix_result);
hidden_results[h] = _map_activation(result);
}
return output_results;
}
float[] normalize_input(float[] inputs){
float sum = 0.0f;
for (int i = 0; i < inputs.Length; i++) {
sum += inputs[i] ;
}
float average = sum / inputs.Length;
float[] deviations = new float[inputs.Length];
for (int i = 0; i < inputs.Length; i++) {
deviations[i] = Mathf.Pow(inputs[i] - average,2);
}
float sum_deviation = 0;
for (int i = 0; i < deviations.Length; i++) {
sum_deviation += deviations[i];
}
float variance = sum_deviation / deviations.Length;
float std = Mathf.Sqrt (variance);
for (int i = 0; i < inputs.Length; i++) {
inputs[i] = (inputs[i] - average)/std;
}
return inputs;
}
public void start_net(float[] inputs){
this.inputs = normalize_input(inputs);
feed_forward ();
}
}
I run the net from other script using the init method and then the start_net() method.
I made a test with not random weights and fixed input data, but it didn't came to the same result as the python code.
What's wrong with the C# code?

Converting a multi-band 16-bit tiff image to an 8-bit tiff image

I got some pixel data from 16-bit(range 0-65535) tif image as an integer array. I got the value using gdal readraster. How do I convert them to 8-bit(0-225) and convert it (the array) to 8-bit tif image ?
Here is some of my code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OSGeo.GDAL;
using OSGeo.OSR;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
Gdal.AllRegister();
Dataset data1;
int xsize, ysize;
int bandsize;
data1 = Gdal.Open("F:\\po_1473547_bgrn_0000000.tif", Access.GA_ReadOnly);
bandsize = data1.RasterCount;
xsize = data1.RasterXSize; //cols
ysize = data1.RasterYSize; //rows
Console.WriteLine("cols : "+xsize+", rows : "+ysize);
Band[] bands = new Band[bandsize];
for (int i = 0; i < bandsize; i++) {
bands[i] = data1.GetRasterBand(i+1);
}
int[,,] pixel = new int[bandsize,xsize,ysize];
int[] pixtemp = new int[xsize * ysize];
for (int i = 0; i < bandsize; i++)
{
bands[i].ReadRaster(0, 0, xsize, ysize, pixtemp, xsize, ysize, 0, 0);
for (int j = 0; j < xsize; j++)
{
for (int k = 0; k < ysize; k++)
{
pixel[i,j,k] = pixtemp[j + k * xsize];
}
}
}
Console.WriteLine("");
for (int i = 0; i < bandsize; i++)
{
Console.WriteLine("some pixel from band " + (i+1));
for (int j = 0; j < 100; j++)
{
Console.Write(" " + pixel[i,100,j]);
}
Console.WriteLine("\n\n");
}
}
}
}
I was searching Google on how to do that but I only found how to do that if the data type is a byte. Someone please give me a hint.
I don't know about GEO Tiff format, but to convert a regular 16 bit tiff image file to an 8 bit one, you need to scale the 16 bit channel values to 8 bits. The example below shows how this can be achieved for gray scale images.
public static class TiffConverter
{
private static IEnumerable<BitmapSource> Load16BitTiff(Stream source)
{
var decoder = new TiffBitmapDecoder(source, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
for (int i = 0; i < decoder.Frames.Count; i++)
// return all frames that are present in the input.
yield return decoder.Frames[i];
}
private static BitmapSource NormalizeTiffTo8BitImage(BitmapSource source)
{
// allocate buffer & copy image bytes.
var rawStride = source.PixelWidth * source.Format.BitsPerPixel / 8;
var rawImage = new byte[rawStride * source.PixelHeight];
source.CopyPixels(rawImage, rawStride, 0);
// get both max values of first & second byte of pixel as scaling bounds.
var max1 = 0;
int max2 = 1;
for (int i = 0; i < rawImage.Length; i++)
{
if ((i & 1) == 0)
{
if (rawImage[i] > max1)
max1 = rawImage[i];
}
else if (rawImage[i] > max2)
max2 = rawImage[i];
}
// determine normalization factors.
var normFactor = max2 == 0 ? 0.0d : 128.0d / max2;
var factor = max1 > 0 ? 255.0d / max1 : 0.0d;
max2 = Math.Max(max2, 1);
// normalize each pixel to output buffer.
var buffer8Bit = new byte[rawImage.Length / 2];
for (int src = 0, dst = 0; src < rawImage.Length; dst++)
{
int value16 = rawImage[src++];
double value8 = ((value16 * factor) / max2) - normFactor;
if (rawImage[src] > 0)
{
int b = rawImage[src] << 8;
value8 = ((value16 + b) / max2) - normFactor;
}
buffer8Bit[dst] = (byte)Math.Min(255, Math.Max(value8, 0));
src++;
}
// return new bitmap source.
return BitmapSource.Create(
source.PixelWidth, source.PixelHeight,
source.DpiX, source.DpiY,
PixelFormats.Gray8, BitmapPalettes.Gray256,
buffer8Bit, rawStride / 2);
}
private static void SaveTo(IEnumerable<BitmapSource> src, string fileName)
{
using (var stream = File.Create(fileName))
{
var encoder = new TiffBitmapEncoder();
foreach (var bms in src)
encoder.Frames.Add(BitmapFrame.Create(bms));
encoder.Save(stream);
}
}
public static void Convert(string inputFileName, string outputFileName)
{
using (var inputStream = File.OpenRead(inputFileName))
SaveTo(Load16BitTiff(inputStream).Select(NormalizeTiffTo8BitImage), outputFileName);
}
}
Usage:
TiffConverter.Convert(#"c:\temp\16bit.tif", #"c:\temp\8bit.tif");
Interpolate pixels from 16 bit to 8 bit, some resampling methods could perform.
Linear Interpolation may help.
//Convert tiff from 16-bit to 8-bit
byte[,,] ConvertBytes(int[,,] pixel, bandsize, xsize, ysize)
{
byte[,,] trgPixel = new byte[bandsize,xsize,ysize];
for (int i = 0; i < bandsize; i++)
{
for (int j = 0; j < xsize; j++)
{
for (int k = 0; k < ysize; k++)
{
//Linear Interpolation
trgPixel[i,j,k] = (byte)((65535-pixel[i,j,k])/65536.0*256);
}
}
}
return trgPixel;
}
//Save 8-bit tiff to file
void SaveBytesToTiff(string destPath, byte[,,] pixel, bandsize, xsize, ysize)
{
string fileformat = "GTiff";
Driver dr = Gdal.getDriverByName(fileformat);
Dataset newDs = dr.Create(destPath, xsize, ysize, bandsize, DateType.GDT_Byte, null);
for(int i=0; i< bandsize;i++)
{
byte[] buffer = new byte[xsize * ysize];
for (int j = 0; j < xsize; j++)
{
for (int k = 0; k < ysize; k++)
{
buffer[j+k*xsize] = pixel[i,j,k];
}
}
newDs.WriteRaster(0, 0, xsize, ysize, buffer, xsize, ysize, i+1, null, 0, 0, 0);
newDs.FlushCache();
}
newDs.Dispose();
}

Categories