I have a server in cpp and a client in Java where I am sending an image from the server to the client in segments. On the client side I pick up all the segment to form a single array of bytes.
My problem is that I don't seem to be able to successfully create a bitmap out of this byte array, since I get null using BitmapFactory.decodeByteArray()
Client code:
byte[] response_bytes;
private Bitmap receive_image ( final String protocol, final int image_size, final int buffer_size)
{
if (image_size <= 0 || buffer_size <= 0)
return null;
Thread image_receiver = new Thread(new Runnable() {
#Override
public void run() {
ByteBuffer byteBuffer = ByteBuffer.allocate(image_size);
byte[] buffer = new byte[buffer_size];
int bytesReadSum = 0;
try {
while (bytesReadSum != image_size) {
activeReader.read(buffer);
String message = new String(buffer);
if (TextUtils.substring(message, 0, len_of_protocol_number).equals(protocol)) {
int bytesToRead = Integer.parseInt(TextUtils.substring(message,
len_of_protocol_number,
len_of_protocol_number + len_of_data_len));
byteBuffer.put(Arrays.copyOfRange(buffer,
len_of_protocol_number + len_of_data_len,
bytesToRead + len_of_protocol_number + len_of_data_len));
bytesReadSum += bytesToRead;
} else {
response_bytes = null;
break;
}
}
if (bytesReadSum == image_size) {
byte[] image_bytes = byteBuffer.array();
if (image_bytes.length > 0)
response_bytes = image_bytes;
else
response_bytes = null;
}
} catch (IOException e) {
response_bytes = null;
}
}
});
image_receiver.start();
try {
image_receiver.join();
} catch (InterruptedException e) {
response_bytes = null;
}
if (response_bytes != null) { //Here image_bitmap is null
Bitmap image_bitmap = BitmapFactory.decodeByteArray(response_bytes, 0, response_bytes.length);
return image_bitmap;
} else {
return null;
}
At this point I'd also like to mention that the image bytes reading seems to work fine and I manage to read all the bytes of the image without fail.
Yet, I'm unable to turn that array of bytes into a bitmap.
Related
I have tried to get the size of the image by .length in java.
However the original size of the image is several bytes higher than that.
What is the reason for this? Is there any code to get the original size?
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import javax.imageio.ImageIO;
public class Array {
public static void main(String argv[]) throws IOException {
String imageFile1 = "C:/Users/Desktop/4.jpg";
File file = new File(imageFile1);
BufferedImage originalImage = ImageIO.read(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(originalImage, "jpg", baos);
byte[] imageInByte = baos.toByteArray();
System.out.println("The length in bytes " + imageInByte.length);
}
}
I think it's about jpeg file header size.
If you want to get an original size when copying image file.
You can use just file copy rather then image file copy.
Or, you can make your own jpeg library, if you really want.
For just one example,
this is one of the old-style code by using java's NIO.
private static void fileCopy(String from, String to) {
FileChannel fromCh = null;
FileChannel toCh = null;
FileInputStream fin = null;
FileOutputStream fout = null;
try {
fin = new FileInputStream(new File(from));
fromCh = fin.getChannel();
fout = new FileOutputStream(new File(to));
toCh = fout.getChannel();
fromCh.transferTo(0, fin.available(), toCh);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fin != null)
try {
fin.close();
} catch (IOException e) {
}
if (fout != null)
try {
fout.close();
} catch (IOException e) {
}
}
}
You can get a file from the original file with the same size.
Check the reference site below:
https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
I tested it the difference between two file, one is the original another is the copy one. I got an jpeg image from googling.
I modified some code form here in order to analyze the jpeg header file .
Here is the method:
final public static ImageProperties getJpegProperties(File file) throws FileNotFoundException, IOException {
BufferedInputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
// check for "magic" header
byte[] buf = new byte[2];
int count = in.read(buf, 0, 2);
if (count < 2) {
throw new RuntimeException("Not a valid Jpeg file!");
}
if ((buf[0]) != (byte) 0xFF || (buf[1]) != (byte) 0xD8) {
throw new RuntimeException("Not a valid Jpeg file!");
}
int width = 0;
int height = 0;
char[] comment = null;
boolean hasDims = false;
boolean hasComment = false;
int ch = 0;
int totalHeaderLen = 0;
while (ch != 0xDA && !(hasDims && hasComment)) {
/* Find next marker (JPEG markers begin with 0xFF) */
while (ch != 0xFF) {
ch = in.read();
}
/* JPEG markers can be padded with unlimited 0xFF's */
while (ch == 0xFF) {
ch = in.read();
}
/* Now, ch contains the value of the marker. */
int length = 256 * in.read();
length += in.read();
totalHeaderLen += length;
if (length < 2) {
throw new RuntimeException("Not a valid Jpeg file!");
}
/* Now, length contains the length of the marker. */
if (ch >= 0xC0 && ch <= 0xC3) {
in.read();
height = 256 * in.read();
height += in.read();
width = 256 * in.read();
width += in.read();
for (int foo = 0; foo < length - 2 - 5; foo++) {
in.read();
}
hasDims = true;
} else if (ch == 0xFE) {
// that's the comment marker
comment = new char[length - 2];
for (int foo = 0; foo < length - 2; foo++)
comment[foo] = (char) in.read();
hasComment = true;
} else {
// just skip marker
for (int foo = 0; foo < length - 2; foo++) {
in.read();
}
}
}
if(comment == null) comment = "no comment".toCharArray();
return (new ImageProperties(width, height, new String(comment), totalHeaderLen, "jpeg"));
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
The main method is :
public static void main(String argv[]) throws IOException {
String imageFile1 = "resource/4.jpg";
String imageFile2 = "resource/4_jpg.jpg";
//copyImage(imageFile1);
ImageProperties origin = getJpegProperties(new File(imageFile1));
ImageProperties copyed = getJpegProperties(new File(imageFile2));
System.out.println("============ Original one ===========");
System.out.println("comments(origin) : " + origin.getComments());
System.out.println("Height(origin) : " + origin.getHeight());
System.out.println("Width(origin) : " + origin.getWidth());
System.out.println("Header Length(origin) : " + origin.getHeaderLen());
//System.out.println("suffix(origin) : " + origin.getSuffix());
System.out.println();
System.out.println("============ Copy one ===========");
System.out.println("comments(copy) : " + copyed.getComments());
System.out.println("Height(copy) : " + copyed.getHeight());
System.out.println("Width(copy) : " + copyed.getWidth());
System.out.println("Header Length(copy) : " + copyed.getHeaderLen());
//System.out.println("suffix(copy) : " + copyed.getSuffix());
}
I copy the original image first using copyImage method here.
static BufferedImage copyImage(BufferedImage source) {
BufferedImage b = new BufferedImage(source.getWidth(), source.getHeight(), source.getType());
Graphics g = b.getGraphics();
g.drawImage(source, 0, 0, null);
g.dispose();
return b;
}
I can see the difference of the two images in the explorer.
I got a different header size when running the program.
The output:
============ Original one ===========
comments(origin) : no comment
Height(origin) : 534
Width(origin) : 800
Header Length(origin) : 21269
============ Copy one ===========
comments(copy) : no comment
Height(copy) : 534
Width(copy) : 800
Header Length(copy) : 603
Header Length is different as you can see the result.
Here is full test code.
package stackoverflow;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class Misc {
public static void main(String argv[]) throws IOException {
String imageFile1 = "resource/4.jpg";
String imageFile2 = "resource/4_jpg.jpg";
String imageFile3 = "resource/4_org.jpg";
fileCopy(imageFile1, imageFile3);
//copyImage(imageFile1);
ImageProperties origin = getJpegProperties(new File(imageFile1));
ImageProperties copyed = getJpegProperties(new File(imageFile2));
System.out.println("============ Original one ===========");
System.out.println("comments(origin) : " + origin.getComments());
System.out.println("Height(origin) : " + origin.getHeight());
System.out.println("Width(origin) : " + origin.getWidth());
System.out.println("Header Length(origin) : " + origin.getHeaderLen());
//System.out.println("suffix(origin) : " + origin.getSuffix());
System.out.println();
System.out.println("============ Copy one ===========");
System.out.println("comments(copy) : " + copyed.getComments());
System.out.println("Height(copy) : " + copyed.getHeight());
System.out.println("Width(copy) : " + copyed.getWidth());
System.out.println("Header Length(copy) : " + copyed.getHeaderLen());
//System.out.println("suffix(copy) : " + copyed.getSuffix());
}
static class ImageProperties {
private final int width;
private final int height;
private final String comments;
private final int headerLen;
private final String suffix;
public ImageProperties(
final int width, final int height, final String comments, final int headerLen,
final String suffix)
{
this.width = width;
this.height = height;
this.comments = comments;
this.suffix = suffix;
this.headerLen = headerLen;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getComments() {
return comments;
}
public String getSuffix() {
return suffix;
}
public int getHeaderLen() {
return headerLen;
}
}
final public static ImageProperties getJpegProperties(File file) throws FileNotFoundException, IOException {
BufferedInputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
// check for "magic" header
byte[] buf = new byte[2];
int count = in.read(buf, 0, 2);
if (count < 2) {
throw new RuntimeException("Not a valid Jpeg file!");
}
if ((buf[0]) != (byte) 0xFF || (buf[1]) != (byte) 0xD8) {
throw new RuntimeException("Not a valid Jpeg file!");
}
int width = 0;
int height = 0;
char[] comment = null;
boolean hasDims = false;
boolean hasComment = false;
int ch = 0;
int totalHeaderLen = 0;
while (ch != 0xDA && !(hasDims && hasComment)) {
/* Find next marker (JPEG markers begin with 0xFF) */
while (ch != 0xFF) {
ch = in.read();
}
/* JPEG markers can be padded with unlimited 0xFF's */
while (ch == 0xFF) {
ch = in.read();
}
/* Now, ch contains the value of the marker. */
int length = 256 * in.read();
length += in.read();
totalHeaderLen += length;
if (length < 2) {
throw new RuntimeException("Not a valid Jpeg file!");
}
/* Now, length contains the length of the marker. */
if (ch >= 0xC0 && ch <= 0xC3) {
in.read();
height = 256 * in.read();
height += in.read();
width = 256 * in.read();
width += in.read();
for (int foo = 0; foo < length - 2 - 5; foo++) {
in.read();
}
hasDims = true;
} else if (ch == 0xFE) {
// that's the comment marker
comment = new char[length - 2];
for (int foo = 0; foo < length - 2; foo++)
comment[foo] = (char) in.read();
hasComment = true;
} else {
// just skip marker
for (int foo = 0; foo < length - 2; foo++) {
in.read();
}
}
}
if(comment == null) comment = "no comment".toCharArray();
return (new ImageProperties(width, height, new String(comment), totalHeaderLen, "jpeg"));
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
static BufferedImage copyImage(BufferedImage source) {
BufferedImage b = new BufferedImage(source.getWidth(), source.getHeight(), source.getType());
Graphics g = b.getGraphics();
g.drawImage(source, 0, 0, null);
g.dispose();
return b;
}
private static void fileCopy(String from, String to) {
FileChannel fromCh = null;
FileChannel toCh = null;
FileInputStream fin = null;
FileOutputStream fout = null;
try {
fin = new FileInputStream(new File(from));
fromCh = fin.getChannel();
fout = new FileOutputStream(new File(to));
toCh = fout.getChannel();
fromCh.transferTo(0, fin.available(), toCh);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fin != null)
try {
fin.close();
} catch (IOException e) {
}
if (fout != null)
try {
fout.close();
} catch (IOException e) {
}
}
}
}
So, I think the extra information of the jpeg image skipped by the image io library in java.
Alright so I am looking at this piece of code that is supposed to get an array of bytes which represents an image and send it piece by piece to a server. The server needs to be told when the image transmission is done, this ending message is "CLOSE". Everything works fine but unless I uncomment Thread.sleep the end message isn't sent. Also the delay needs to be quite big for some reason, 100 ms for example doesn't work. If anyone could provide an explanation for this behaviour I would be grateful since I don't have a very good understanding of java.
private class NetTask extends AsyncTask<Void, Void, Void>
{
private String ip;
private byte[] to_send;
public NetTask(String ip, byte[] to_send)
{
this.ip = ip;
this.to_send = to_send;
}
#Override
protected Void doInBackground(Void...params)
{
try {
Log.i(dTag, "" + to_send.length);
Socket sck = new Socket(ip, 1234);
DataOutputStream dOut = new DataOutputStream(sck.getOutputStream());
dOut.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(to_send.length).array());
Log.d(dTag, "" + to_send.length);
int x = 500;
int len = to_send.length;
for (int i = 0; i < len - x + 1; i += x)
dOut.write(Arrays.copyOfRange(to_send, i, i + x));
if (len % x != 0)
dOut.write(Arrays.copyOfRange(to_send, len - len % x, len));
/*try {
Thread.sleep(1000);
}
catch (Exception ex) {
Log.d(dTag, "thread sleep error");
}*/
dOut.write("CLOSE".getBytes());
dOut.flush();
dOut.close();
sck.close();
}
catch (IOException ex) {
Log.d(dTag, ex.getMessage());
}
return null;
}
}
The server is in c#, here is the code:
while (ok)
{
sck.Listen(1000);
Socket accepted = sck.Accept();
buffer = new byte[accepted.SendBufferSize];
int bytesRead = -1;
bool reading = true;
int im_size = -1;
int index = 0;
byte[] image = null;
while (reading)
{
bytesRead = accepted.Receive(buffer);
if (bytesRead == 5)
Console.WriteLine(bytesRead);
string strData = "YADA";
byte[] formatted = new byte[bytesRead];
if (bytesRead == 5)
{
for (int i = 0; i < bytesRead; i++)
{
formatted[i] = buffer[i];
}
strData = Encoding.ASCII.GetString(formatted);
}
if (strData == "CLOSE")
{
Console.WriteLine("GOT CLOSE MESSAGE");
Image im = Image.FromStream(new MemoryStream(image));
im.Save(#"D:\im1.bmp");
}
else
{
if (im_size == -1)
{
im_size = BitConverter.ToInt32(buffer, 0);
image = new byte[im_size];
Console.WriteLine(im_size);
}
else
{
for (int i = 0; i < bytesRead && index < im_size; i++)
{
image[index++] = buffer[i];
}
}
}
}
accepted.Close();
}
I am getting live audio streaming over the network in the form of RTP packets and I have to write a code to Capture, Buffer and play the audio stream.
Problem
Now to solve this problem I have written two threads one for capture the audio and another for playing it. Now when I start both the threads my capture threads running slower than playing thread :(
Buffer Requirement
RTP Audio Packets.
8kHz, 16-bit Linear Samples (Linear PCM).
4 frames of 20ms audio will be sent in each RTP Packet.
Do not play until AudioStart=24 (# of 20ms frames) have arrived.
While playing ... if the # of 20ms frames in buffer reaches 0 ...
stop playing until AudioStart frames are buffered then restart.
While playing ... if the # of 20ms frames in buffer exceeds
AudioBufferHigh=50 then delete 24 frames (in easiest manner -- delete
from buffer or just drop next 6 RTP messages).
What I have done so far..
Code
BufferManager.java
public abstract class BufferManager {
protected static final Integer ONE = new Integer(1);
protected static final Integer TWO = new Integer(2);
protected static final Integer THREE = new Integer(3);
protected static final Integer BUFFER_SIZE = 5334;//5.334KB
protected static volatile Map<Integer, ByteArrayOutputStream> bufferPool = new ConcurrentHashMap<>(3, 0.9f, 2);
protected static volatile Integer captureBufferKey = ONE;
protected static volatile Integer playingBufferKey = ONE;
protected static Boolean running;
protected static volatile Integer noOfFrames = 0;
public BufferManager() {
//captureBufferKey = ONE;
//playingBufferKey = ONE;
//noOfFrames = new Integer(0);
}
protected void switchCaptureBufferKey() {
if(ONE.intValue() == captureBufferKey.intValue())
captureBufferKey = TWO;
else if(TWO.intValue() == captureBufferKey.intValue())
captureBufferKey = THREE;
else
captureBufferKey = ONE;
//printBufferState("SWITCHCAPTURE");
}//End of switchWritingBufferKey() Method.
protected void switchPlayingBufferKey() {
if(ONE.intValue() == playingBufferKey.intValue())
playingBufferKey = TWO;
else if(TWO.intValue() == playingBufferKey.intValue())
playingBufferKey = THREE;
else
playingBufferKey = ONE;
}//End of switchWritingBufferKey() Method.
protected static AudioFormat getFormat() {
float sampleRate = 8000;
int sampleSizeInBits = 16;
int channels = 1;
boolean signed = true;
boolean bigEndian = true;
return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
}
protected int getByfferSize() {
return bufferPool.get(ONE).size()
+ bufferPool.get(TWO).size()
+ bufferPool.get(THREE).size();
}
protected static void printBufferState(String flag) {
int a = bufferPool.get(ONE).size();
int b = bufferPool.get(TWO).size();
int c = bufferPool.get(THREE).size();
System.out.println(flag + " == TOTAL : [" + (a + b +c) + "bytes] ");
// int a,b,c;
// System.out.println(flag + "1 : [" + (a = bufferPool.get(ONE).size()) + "bytes], 2 : [" + (b = bufferPool.get(TWO).size())
// + "bytes] 3 : [" + (c = bufferPool.get(THREE).size()) + "bytes], TOTAL : [" + (a + b +c) + "bytes] ");
}
}//End of BufferManager Class.
AudioCapture.java
public class AudioCapture extends BufferManager implements Runnable {
private static final Integer RTP_HEADER_SIZE = 12;
private InetAddress ipAddress;
private DatagramSocket serverSocket;
long lStartTime = 0;
public AudioCapture(Integer port) throws UnknownHostException, SocketException {
super();
running = Boolean.TRUE;
bufferPool.put(ONE, new ByteArrayOutputStream(BUFFER_SIZE));
bufferPool.put(TWO, new ByteArrayOutputStream(BUFFER_SIZE));
bufferPool.put(THREE, new ByteArrayOutputStream(BUFFER_SIZE));
this.ipAddress = InetAddress.getByName("0.0.0.0");
serverSocket = new DatagramSocket(port, ipAddress);
}
#Override
public void run() {
System.out.println();
byte[] receiveData = new byte[1300];
DatagramPacket receivePacket = null;
lStartTime = System.currentTimeMillis();
receivePacket = new DatagramPacket(receiveData, receiveData.length);
byte[] packet = new byte[receivePacket.getLength() - RTP_HEADER_SIZE];
ByteArrayOutputStream buff = bufferPool.get(captureBufferKey);
while (running) {
if(noOfFrames <= 50) {
try {
serverSocket.receive(receivePacket);
packet = Arrays.copyOfRange(receivePacket.getData(), RTP_HEADER_SIZE, receivePacket.getLength());
if((buff.size() + packet.length) > BUFFER_SIZE) {
switchCaptureBufferKey();
buff = bufferPool.get(captureBufferKey);
}
buff.write(packet);
noOfFrames += 4;
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} // End of try-catch block.
} else {
//System.out.println("Packet Ignored, Buffer reached to its maximum limit ");
}//End of if-else block.
} // End of while loop.
}//End of run() Method.
}
AudioPlayer.java
public class AudioPlayer extends BufferManager implements Runnable {
long lStartTime = 0;
public AudioPlayer() {
super();
}
#Override
public void run() {
AudioFormat format = getFormat();
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
SourceDataLine line = null;
try {
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();
} catch (LineUnavailableException e1) {
e1.printStackTrace();
}
while (running) {
if (noOfFrames >= 24) {
ByteArrayOutputStream out = null;
try {
out = bufferPool.get(playingBufferKey);
InputStream input = new ByteArrayInputStream(out.toByteArray());
byte buffer[] = new byte[640];
int count;
while ((count = input.read(buffer, 0, buffer.length)) != -1) {
if (count > 0) {
InputStream in = new ByteArrayInputStream(buffer);
AudioInputStream ais = new AudioInputStream(in, format, buffer.length / format.getFrameSize());
byte buff[] = new byte[640];
int c = 0;
if((c = ais.read(buff)) != -1)
line.write(buff, 0, buff.length);
}
}
} catch (IOException e) {
e.printStackTrace();
}
/*byte buffer[] = new byte[1280];
try {
int count;
while ((count = ais.read(buffer, 0, buffer.length)) != -1) {
if (count > 0) {
line.write(buffer, 0, count);
}
}
} catch (IOException e) {
e.printStackTrace();
}*/
out.reset();
noOfFrames -= 4;
try {
if (getByfferSize() >= 10240) {
Thread.sleep(15);
} else if (getByfferSize() >= 5120) {
Thread.sleep(25);
} else if (getByfferSize() >= 0) {
Thread.sleep(30);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// System.out.println("Number of frames :- " + noOfFrames);
}
}
}// End of run() method.
}// End of AudioPlayer Class class.
any help or pointer to the helpful link will be appreciable Thanks...
This answer explains a few challenges with streaming.
In a nutshell, your client needs to deal with two issues:
1) The clock (crystals) on the client and server are not perfectly in sync. The server may be a fraction of a Hz faster/slower than the client. The client continuously match the infer the clock rate of the server by examining the rate that rtp packets are delivered. The client then adjusts the playback rate via sample rate conversion. So instead of playing back at 48k, it may play back at 48000.0001 Hz.
2) Packets loss, out of order arrivals, etc. must be dealt with. If you lose packets, you need to still keep a place holder for those packets in your buffer stream otherwise your audio will skip and sound crackly and become unaligned. The simplest method would be to replace those missing packets with silence but the volume of adjacent packets should be adjusted to avoid sharp envelope changes snapping to 0.
Your design seems a bit unorthodox. I have had success using a ring buffer instead. You will have to deal with edge cases as well.
I always state that streaming media is not a trivial task.
Hey guys so I'm trying to read a stream from a bluetooth device continuously streaming integers like this:
-11
121
123
1234
-11
I have everything working with some code I found online, but to do some of the processing the numbers need to be ints as opposed to Strings, parseInt is taking up too much CPU, and I tried using a buffered stream with no avail.
Here is the current method:
void beginListenForData()
{
final Handler handler = new Handler();
final byte delimiter = 10; //This is the ASCII code for a newline character
stopWorker = false;
readBufferPosition = 0;
readBuffer = new byte[1024];
workerThread = new Thread(new Runnable()
{
public void run()
{
while(!Thread.currentThread().isInterrupted() && !stopWorker)
{
try
{
int bytesAvailable = mmInputStream.available();
if(bytesAvailable > 0)
{
byte[] packetBytes = new byte[bytesAvailable];
mmInputStream.read(packetBytes);
for(int i=0;i<bytesAvailable;i++)
{
byte b = packetBytes[i];
if(b == delimiter)
{
byte[] encodedBytes = new byte[readBufferPosition];
System.arraycopy(readBuffer, 0, encodedBytes, 0, encodedBytes.length);
final String data = new String(encodedBytes, "US-ASCII");
readBufferPosition = 0;
handler.post(new Runnable()
{
public void run()
{
myLabel.setText(data);
}
});
}
else
{
readBuffer[readBufferPosition++] = b;
}
}
}
}
catch (IOException ex)
{
stopWorker = true;
}
}
}
});
workerThread.start();
}
If it makes a difference, the data is coming from an Arudino, which I can modify the way it streams.
Thanks!
Use a DataInputStream wrapped around a BufferedInputStream, and the readInt() method. This assumes network byte order in the integers of course.
Forget all this arraycopy() stuff.
I want to serve image files that saved on "external storage" via ContentProvider.
These image files are 'mangled' - first 50 bytes are XORed with some arbitary value. I want to do 'demangle' within ContentProvider so that other applications don't need to do special treatment.
I'm using Mininum SDK version 14.
Here is my first try - using piped ParcelFileDescriptor:
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// basic uri/mode check here
return openPipeHelper(uri, getType(uri), null, new FileInputStream(getImageFile(uri)), new PipeDataWriter<InputStream>() {
#Override
public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, final String mimeType, Bundle opts, InputStream input) {
InputStream fin = new FilterInputStream(input) {
private int cnt = 0;
private byte mask;
#Override
public int read() throws IOException {
byte[] buffer = new byte[1];
return read(buffer) == -1 ? -1 : (buffer[0] & 0xff);
}
#Override
public int read(#NonNull byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
#Override
public int read(#NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
int ret = super.read(buffer, byteOffset, byteCount);
if (ret <= 0) return ret;
if (cnt == 0) {
switch (mimeType) {
case "image/png":
mask = (byte) (buffer[byteOffset] ^ 137);
break;
case "image/webp":
mask = (byte) (buffer[byteOffset] ^ 'R');
break;
}
}
for (int i = byteOffset; i < byteOffset + ret && cnt < 50; i++, cnt++) {
buffer[i] ^= mask;
}
return ret;
}
};
OutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(output);
byte[] buf = new byte[1024 * 1024];
try {
while (true) {
int n = fin.read(buf);
if (n == -1) break;
Log.i(TAG, "openFile get n=" + n);
fout.write(buf, 0, n);
fout.flush();
}
} catch (IOException ex) {
// EPIPE likely means pipe closed on other end; treat it as WAI.
if (!ex.getMessage().contains("EPIPE")) {
Log.w(TAG, "openFile failed", ex);
}
} finally {
try {
fin.close();
} catch (IOException ex) {
Log.w(TAG, "openFile failed closing input", ex);
}
try {
fout.close();
} catch (IOException ex) {
Log.w(TAG, "openFile failed closing output", ex);
}
}
}
});
}
Result:
Works well with ImageView.setImageURI().
Don't work with android default Gallery (Intent.ACTION_VIEW with setDataAndType())
Works well with ES image viewer
It seems that Gallery don't like "piped stream".
Here is second try - read whole file, demangle, and serve as ParcelFileDescriptor.fromData():
File file = getImageFile(uri);
byte[] buffer = readFully(file);
String mimeType = getType(uri);
byte mask;
switch (mimeType) {
case "image/png":
mask = (byte) (buffer[0] ^ 137);
break;
case "image/webp":
mask = (byte) (buffer[0] ^ 'R');
break;
default:
mask = 0;
break;
}
for (int i = 0; i < 50; i++) buffer[i] ^= mask;
return (ParcelFileDescriptor) ParcelFileDescriptor.class.getMethod("fromData", byte[].class, String.class).invoke(null, buffer, getImageFile(uri).getName());
Result:
Don't work well with ImageView.setImageURI().
Works well with android default Gallery
Works well with ES image viewer
It seems that from time to time, MemoryFile made in ParcelFileDescriptor.fromData() is closed and disposed before ImageView.setImageURI() get data.
Here is third try - write demangled image to temporary file:
// buffer contains readFully and demangled image binary
try {
File tmpFile = File.createTempFile("image", getImageExtension(uri));
OutputStream os = new FileOutputStream(tmpFile);
try {
os.write(buffer);
} finally {
try {
os.close();
} catch (IOException ex2) {
Log.w(TAG, "openFile(): closing failed", ex2);
}
}
return ParcelFileDescriptor.open(tmpFile, ParcelFileDescriptor.MODE_READ_ONLY);
} catch (IOException ex2) {
Log.e(TAG, "openFile(): writing failed", ex2);
return null;
}
Result:
Works well with ImageView.setImageURI().
Works well with android default Gallery
Works well with ES image viewer
However, I don't like this solution, as it is very hard to determine when I could delete temporary files.
These 3 solutions have their flaws, and I couldn't find flawless solution. What is the "right" way to do such things?