Give a file as input to Pocketsphinx on Android - pocketsphinx-android

I am using the latest pocketsphinx android demo (mighty computer),which takes input from microphone. I want to give a wav file as input to the same. I tried using decoder.processrow() function. But I don't know how to configure the decoder using hmm, lm etc.

Code to process files in pocketsphinx-java
Config c = Decoder.defaultConfig();
c.setString("-hmm", "../../model/en-us/en-us");
c.setString("-lm", "../../model/en-us/en-us.lm.dmp");
c.setString("-dict", "../../model/en-us/cmudict-en-us.dict");
Decoder d = new Decoder(c);
URL testwav = new URL("file:../../test/data/goforward.wav");
FileInputStream stream = new FileInputStream(new File(testwav)));
d.startUtt();
byte[] b = new byte[4096];
try {
int nbytes;
while ((nbytes = stream.read(b)) >= 0) {
ByteBuffer bb = ByteBuffer.wrap(b, 0, nbytes);
// Not needed on desktop but required on android
bb.order(ByteOrder.LITTLE_ENDIAN);
short[] s = new short[nbytes/2];
bb.asShortBuffer().get(s);
d.processRaw(s, nbytes/2, false, false);
}
} catch (IOException e) {
fail("Error when reading goforward.wav" + e.getMessage());
}
d.endUtt();
System.out.println(d.hyp().getHypstr());
for (Segment seg : d.seg()) {
System.out.println(seg.getWord());
}
}

Adding to the answer from Nikolay, this is how it can be done on Android, adapting the SpeechRecognizer Android implementation example found here: http://cmusphinx.sourceforge.net/wiki/tutorialandroid
//statically load our library
static {
System.loadLibrary("pocketsphinx_jni");
}
//convert an inputstream to text
private void convertToSpeech(final InputStream stream){
new AsyncTask<Void, Void, Exception>() {
#Override
protected Exception doInBackground(Void... params) {
try {
Assets assets = new Assets(WearService.this);
File assetsDir = assets.syncAssets();
Config c = Decoder.defaultConfig();
c.setString("-hmm", new File(assetsDir, "en-us-ptm").getPath());
c.setString("-dict", new File(assetsDir, "cmudict-en-us.dict").getPath());
c.setBoolean("-allphone_ci", true);
c.setString("-lm", new File(assetsDir, "en-phone.dmp").getPath());
Decoder d = new Decoder(c);
d.startUtt();
byte[] b = new byte[4096];
try {
int nbytes;
while ((nbytes = stream.read(b)) >= 0) {
ByteBuffer bb = ByteBuffer.wrap(b, 0, nbytes);
// Not needed on desktop but required on android
bb.order(ByteOrder.LITTLE_ENDIAN);
short[] s = new short[nbytes/2];
bb.asShortBuffer().get(s);
d.processRaw(s, nbytes/2, false, false);
}
} catch (IOException e) {
fail("Error when reading inputstream" + e.getMessage());
}
d.endUtt();
System.out.println(d.hyp().getHypstr());
for (Segment seg : d.seg()) {
//do something with the result here
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

Related

OpenCV [[mjpeg # 0000000000428480] overread 8] during reading a frame from camera

I have a very annoying OpenCV error, that i can't understand, and handle with.
I write an application which gets mjpg's stream from ip camera, and process it, but when i try to load image from stream, sometimes i have
[mjpeg # 0000000000428480] overread 8
error, and i don't know why.
Even if i try to skip this issue, and try to load next frame from the stream, the application stucks on
frameStatus = cameraHandler->read(mat);
This is code for connection establishing:
void ImageProcessor::connectWithCamera(VideoCapture * cameraHandler) {
if (cameraHandler != nullptr) {
Logger::log("Closing existing camera stream.");
cameraHandler->release();
delete cameraHandler;
}
Logger::log("Camera configuration and connection establishing.");
cameraHandler = new VideoCapture();
cameraHandler->set(CV_CAP_PROP_FRAME_WIDTH, config.RESOLUTION_WIDTH);
cameraHandler->set(CV_CAP_PROP_FRAME_HEIGHT, config.RESOLUTION_HEIGHT);
cameraHandler->set(CV_CAP_PROP_FPS, config.CAMERA_FPS);
cameraHandler->set(CV_CAP_PROP_FOURCC, CV_FOURCC('M', 'J', 'P', 'G'));
while (!cameraHandler->open(config.LINK)) {
Logger::log("Cannot connect to camera! Trying again.");
}
}
And this is code for capturing images:
void ImageProcessor::start() {
VideoCapture * cameraHandler = new VideoCapture();
this->connectWithCamera(cameraHandler);
this->connectWithServer(this->serverConnection);
Logger::log("Id sending.");
serverConnection->send(config.TOKEN + "\n");
Logger::log("Computations starting.");
Mat mat;
Result * result = nullptr;
int delta = 1000 / cameraHandler->get(CV_CAP_PROP_FPS);
char frameErrorCounter = 0;
bool frameStatus;
while (true) {
frameStatus = false;
cv::waitKey(delta);
try {
frameStatus = cameraHandler->read(mat);
} catch (std::exception& e) {
std::string message = e.what();
Logger::log("Critical camera error! : " + message);
}
if (!frameStatus) {
Logger::log("Cannot read a frame from source. ");
++frameErrorCounter;
if (!cameraHandler->isOpened() || frameErrorCounter >= this->GET_FRAME_ERROR_COUNTER) {
Logger::log("Probably camera is disconnected. Trying to establish connection again.");
frameErrorCounter = 0;
this->connectWithCamera(cameraHandler);
Logger::log("Computations starting.");
}
continue;
}
result = processImage(mat);
std::string stringResult;
if (result == nullptr) {
stringResult = this->NO_RESULT;
delete result;
result = nullptr;
} else {
stringResult = result->toJson();
}
if (!serverConnection->send(stringResult)) {
Logger::log("Server connection lost, trying to establish it again.");
serverConnection->close();
while (!serverConnection->isOpen()) {
this->connectWithServer(serverConnection);
}
}
mat.release();
}
}
Thanks in advance!

How to custom a classloader

I'm trying to implementing this function with a customer classloader: I have some class files in a alternatives.jar file, they provide different implementation than normal implementation. i.e., each class in this jar, has another version which in other jar file -- also get loaded in the classpath.
I know it's better to use instrument API to achieve same purpose. But now my concern is I need to understand why I'm failing.
So this is my method:
1. define a AlternativeClassLoader.java, in this file, I override findClass method. So if the class name can be found from alternatives.jar, then use the version from alternatives.jar.
2. in constructor, I have called super(null) so all these class loading work will be performed by my classloader, rather that system's.
3. This (seems to be true) also requires me to load other classes (if they're not system one). So I have to parse classpath, find all classes which indicated by the classpath.
My problem is, I can load my alternative class, everything seems to be fine...However, I'm using slf4j which yells the following error:
Failed to auto configure default logger context
Reported exception:
ch.qos.logback.core.joran.spi.JoranException: Parser configuration error occurred
Failed to instantiate [ch.qos.logback.classic.LoggerContext]
Reported exception:
java.lang.ExceptionInInitializerError
at java.util.ResourceBundle.getLoader(ResourceBundle.java:431)
at java.util.ResourceBundle.getBundle(ResourceBundle.java:841)
I doubt this is caused by my bad classloader implementation. Would somebody help me out? Many thanks!
This is my classloader:
public class AlternativeClassLoader extends ClassLoader {
private static final String ALTERNATIVE_JAR_PROPERTY = "alternativejar";
private static final Logger logger = LoggerFactory
.getLogger(AlternativeClassLoader.class);
private Map<String, Class<?>> clzCache = new HashMap<String, Class<?>>();
private Map<String, String> others = new HashMap<String, String>();
private Set<String> alternativesRegistry;
private JarFile altjar;
public AlternativeClassLoader(ClassLoader parent) {
/*
* pass null so I can incept all class loading except system's. By doing
* this you'll need to override findClass
*/
super(null);
registerAlternatives();
registerOthers();
}
/**
* This method will parse classpath and get all non-system class name, and
* build classname - jar_file_path/file_system_path mappings
*/
private void registerOthers() {
String[] paths = System.getProperty("java.class.path").split(":");
URL[] urls = new URL[paths.length];
for (String path : paths) {
if (path.endsWith("*.jar")) {
registerClass(path, others);
} else {
File f = new File(path);
if (!f.isDirectory())
continue;
File[] classFiles = f.listFiles(new FileFilter() {
#Override
public boolean accept(File arg0) {
if (arg0.getName().endsWith(".class")) {
return true;
} else {
return false;
}
}
});
for (File file : classFiles) {
String fileName = file.getName();
String className = fileName.substring(0,
fileName.lastIndexOf("."));
others.put(className, file.getPath());
}
}
}
showRegistry(
"Me will also be responsible for loading the following classes:",
others);
}
private void registerClass(String path, Map<String, String> registry) {
try {
JarInputStream jis = new JarInputStream(new FileInputStream(path));
for (JarEntry entry = jis.getNextJarEntry(); entry != null; entry = jis
.getNextJarEntry()) {
if (entry.getName().endsWith(".class") && !entry.isDirectory()) {
StringBuilder className = new StringBuilder();
for (String part : entry.getName().split("/")) {
if (className.length() != 0)
className.append(".");
className.append(part);
if (part.endsWith(".class"))
className.setLength(className.length()
- ".class".length());
}
registry.put(className.toString(), path);
}
}
} catch (Exception e) {
e.printStackTrace(System.out);
logger.error(
"Failed when read/parse jar {}. Your class file may not been replaced by alternative implementation",
path, e);
}
}
/**
* Try to find alternative class implementation from jar file specified by
* ALTERNATIVE_JAR_PROPERTY. If it's not specified, then use same jar file
* where this classloader is loaded.
*/
private void registerAlternatives() {
String jarFilePath = System.getProperty(ALTERNATIVE_JAR_PROPERTY);
if (jarFilePath == null || jarFilePath.isEmpty()) {
URL url = getClass().getProtectionDomain().getCodeSource()
.getLocation();
System.out.println(url + ":" + url.toString());
jarFilePath = url.getPath();
}
try {
altjar = new JarFile(jarFilePath);
} catch (IOException e) {
logger.error("cannot read jar {}", jarFilePath);
return;
}
Map<String, String> registry = new HashMap<String, String>();
registerClass(jarFilePath, registry);
alternativesRegistry = registry.keySet();
showRegistry("===Found the following alternative class:===", registry);
}
private void showRegistry(String string, Map<String, String> registry) {
System.out.println(string);
for (String clzName : registry.keySet()) {
System.out.printf("Class:%30s ->%s\n", clzName,
registry.get(clzName));
}
}
private Class<?> myLoadClass(String name) throws IOException,
ClassFormatError {
logger.debug("myload class {}", name);
System.out.printf("myload class %s\n", name);
if (alternativesRegistry.contains(name) && altjar != null) {
JarEntry entry = altjar.getJarEntry(name + ".class");
InputStream is = altjar.getInputStream(entry);
return readClassData(name, is);
}
String path = others.get(name);
if (path == null || path.isEmpty()) {
return null;
}
if (path.endsWith(".jar")) {
JarFile jar = new JarFile(path);
JarEntry entry = jar.getJarEntry(name + ".class");
InputStream is = jar.getInputStream(entry);
return readClassData(name, is);
} else {// it's a folder, need to read clz from .class file
System.out.printf("file path for %s is %s\n", name, path);
InputStream is = new FileInputStream(new File(path));
return readClassData(name, is);
}
}
private Class<?> readClassData(String name, InputStream is)
throws IOException, ClassFormatError {
byte[] buffer = new byte[4096];
ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length);
int len = is.read(buffer);
while (len > 0) {
out.write(buffer, 0, len);
len = is.read(buffer);
}
Class<?> clz = defineClass(name, out.toByteArray(), 0, out.size());
if (clz != null) {
System.out.printf("loaded %s by me\n", name);
clzCache.put(name, clz);
}
return clz;
}
protected Class<?> findCachedClass(String name)
throws ClassNotFoundException {
Class<?> clz = clzCache.get(name);
if (clz == null) {
clz = findLoadedClass(name);
}
return clz;
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("findClass: " + name);
Class<?> cls = findCachedClass(name);
if (cls == null) {
try {
cls = myLoadClass(name);
} catch (ClassFormatError | IOException e) {
logger.error("failed to load class {}", name, e);
System.out.printf("failed to load class %s\n", name);
e.printStackTrace();
}
}
return cls;
}
}
I have tried to override findResource(), but it's never called.
This is how I put my classloader into use:
java -Djava.system.class.loader=AlternativeClassLoader -classpath=.:./alternatives.jar:./slf4j-xxx.jar Test
OK, I solved the problem. The tricky is:
Never use any package other than java.*. Otherwise, it will cause recursively loading ...IllegalState error.
In your classloader constructor, load all the alternative class and cache them.
In your constructor, call super(parent) other than super(null), then you don't need to do all the class loading stuff, the parent classloader can do it for you.
in override findClass(), if the class can be found from cache (means they have alternative implementation), then return it, otherwise let super.findClass do the rest for you.
so the following is the source code:
public class AlternativeClassLoader extends ClassLoader {
private static final String ALTERNATIVE_JAR_PROPERTY = "alternativejar";
private Map<String, Class<?>> clzCache = new HashMap<String, Class<?>>();
public AlternativeClassLoader(ClassLoader parent) {
super(parent);
loadAlternativeClasses();
}
private void loadAlternativeClasses() {
String jarFilePath = System.getProperty(ALTERNATIVE_JAR_PROPERTY);
if (jarFilePath == null || jarFilePath.isEmpty()){
URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
System.out.println(url + ":" + url.toString());
jarFilePath = url.getPath();
}
JarInputStream jis;
try {
jis = new JarInputStream(new FileInputStream(jarFilePath));
JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null){
String className = entry.getName();
className = className.substring(0, className.length() - ".class".length());
System.out.printf("loading class from %s: %s\n", jarFilePath, className);
readClassData(className, jis);
}
} catch (IOException e) {
e.printStackTrace();
}
} private Class<?> readClassData(String name, InputStream is) throws IOException,
ClassFormatError {
byte[] buffer = new byte[4096];
ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length);
int len = is.read(buffer);
while (len > 0) {
out.write(buffer, 0, len);
len = is.read(buffer);
}
Class<?> clz = defineClass(name, out.toByteArray(), 0, out.size());
if (clz != null) {
System.out.printf("loaded %s by myself\n", name);
clzCache.put(name, clz);
}
return clz;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("findClass: " + name);
Class<?> cls = clzCache.get(name);
if (cls == null)
cls = super.findClass(name);
return cls;
}
}

Files locked after indexing

I have the following workflow in my (web)application:
download a pdf file from an archive
index the file
delete the file
My problem is that after indexing the file, it remains locked and the delete-part throws an exception.
Here is my code-snippet for indexing the file:
try
{
ContentStreamUpdateRequest req = new ContentStreamUpdateRequest("/update/extract");
req.addFile(file, type);
req.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
NamedList<Object> result = server.request(req);
Assert.assertEquals(0, ((NamedList<?>) result.get("responseHeader")).get("status"));
}
Do I miss something?
EDIT:
I tried this way too, but with the same result...
ContentStream contentStream = null;
try
{
contentStream = new ContentStreamBase.FileStream(document);
ContentStreamUpdateRequest req = new ContentStreamUpdateRequest(UPDATE_EXTRACT_REQUEST);
// req.addFile(document, context.getProperty(FTSConstants.CONTENT_TYPE_APPLICATION_PDF));
req.addContentStream(contentStream);
req.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
NamedList<Object> result = server.request(req);
if (!((NamedList<?>) result.get("responseHeader")).get("status").equals(0))
{
throw new IDSystemException(LOG, "Document could not be indexed. Status returned: " +
((NamedList<?>) result.get("responseHeader")).get("status"));
}
}
catch (FileNotFoundException fnfe)
{
throw new IDSystemException(LOG, fnfe.getMessage(), fnfe);
}
catch (IOException ioe)
{
throw new IDSystemException(LOG, ioe.getMessage(), ioe);
}
catch (SolrServerException sse)
{
throw new IDSystemException(LOG, sse.getMessage(), sse);
}
finally
{
try
{
if(contentStream != null && contentStream.getStream() != null)
{
contentStream.getStream().close();
}
}
catch (IOException ioe)
{
throw new IDSystemException(LOG, ioe.getMessage(), ioe);
}
}
This seems like a bug,
a patch is proposed here
https://issues.apache.org/jira/browse/SOLR-1744
Also checkout
http://lucene.472066.n3.nabble.com/ContentStreamUpdateRequest-addFile-fails-to-close-Stream-td485429.html
you can check if the stream is not null and close it.
It may be due to lock acquired by file system. Instead of addFile(), you can try the following.
ContentStreamUpdateRequest req = new ContentStreamUpdateRequest("/update/extract");
ContentStreamBase.FileStream fileStream = new FileStream(file);
req.addContentStream(fileStream);
Shishir

TTS over web-service in compressed format

I have developed TTS engine in .NET. Now I want to expose it over web.
I have used the base64 string encoding to transfer the WAV format, but it is slow when I pass longer text.
Now I'm considering to build some MP3 streaming (maybe with NAudio) where I will convert the WAV formated MemoryStream into MP3 stream and pass it to the client. Does anyone has some experience with this?
Does anyone has experience how to convert WAV MemoryStream with NAudio to MP3 MemoryStream?
public class MP3StreamingPanel2 : UserControl
{
enum StreamingPlaybackState
{
Stopped,
Playing,
Buffering,
Paused
}
private BufferedWaveProvider bufferedWaveProvider;
private IWavePlayer waveOut;
private volatile StreamingPlaybackState playbackState;
private volatile bool fullyDownloaded;
private HttpWebRequest webRequest;
public void StreamMP32(string url)
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
SettingsSection section = (SettingsSection)config.GetSection("system.net/settings");
section.HttpWebRequest.UseUnsafeHeaderParsing = true;
config.Save();
this.fullyDownloaded = false;
webRequest = (HttpWebRequest)WebRequest.Create(url);
int metaInt = 0; // blocksize of mp3 data
webRequest.Headers.Clear();
webRequest.Headers.Add("GET", "/ HTTP/1.0");
// needed to receive metadata informations
webRequest.Headers.Add("Icy-MetaData", "1");
webRequest.UserAgent = "WinampMPEG/5.09";
HttpWebResponse resp = null;
try
{
resp = (HttpWebResponse)webRequest.GetResponse();
}
catch (WebException e)
{
if (e.Status != WebExceptionStatus.RequestCanceled)
{
//ShowError(e.Message);
}
return;
}
byte[] buffer = new byte[16384 * 4]; // needs to be big enough to hold a decompressed frame
try
{
// read blocksize to find metadata block
metaInt = Convert.ToInt32(resp.GetResponseHeader("icy-metaint"));
}
catch
{
}
IMp3FrameDecompressor decompressor = null;
try
{
using (var responseStream = resp.GetResponseStream())
{
var readFullyStream = new ReadFullyStream(responseStream);
readFullyStream.metaInt = metaInt;
do
{
if (bufferedWaveProvider != null && bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes < bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4)
{
Debug.WriteLine("Buffer getting full, taking a break");
Thread.Sleep(500);
}
else
{
Mp3Frame frame = null;
try
{
frame = Mp3Frame.LoadFromStream(readFullyStream, true);
}
catch (EndOfStreamException)
{
this.fullyDownloaded = true;
// reached the end of the MP3 file / stream
break;
}
catch (WebException)
{
// probably we have aborted download from the GUI thread
break;
}
if (decompressor == null)
{
// don't think these details matter too much - just help ACM select the right codec
// however, the buffered provider doesn't know what sample rate it is working at
// until we have a frame
WaveFormat waveFormat = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frame.FrameLength, frame.BitRate);
decompressor = new AcmMp3FrameDecompressor(waveFormat);
this.bufferedWaveProvider = new BufferedWaveProvider(decompressor.OutputFormat);
this.bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(20); // allow us to get well ahead of ourselves
//this.bufferedWaveProvider.BufferedDuration = 250;
}
int decompressed = decompressor.DecompressFrame(frame, buffer, 0);
//Debug.WriteLine(String.Format("Decompressed a frame {0}", decompressed));
bufferedWaveProvider.AddSamples(buffer, 0, decompressed);
}
} while (playbackState != StreamingPlaybackState.Stopped);
Debug.WriteLine("Exiting");
// was doing this in a finally block, but for some reason
// we are hanging on response stream .Dispose so never get there
decompressor.Dispose();
}
}
finally
{
if (decompressor != null)
{
decompressor.Dispose();
}
}
}
}
NAudio does not include an MP3 encoder. When I need to encode MP3 I use lame.exe. If you don't want to go via a file, lame.exe allows you to read from stdin and write to stdout, so if you redirect standard in and out on the process you can convert on the fly.

Silverlight 4 image upload problem

I am using Silverlight4 with java webervices in jsp page. I want to save an image to the server so trying to do this with java webservice. I am using below lines of code but output is damaged. I dont understand why. Please help me. This is really important for me. When i try to open 3mb jpeg file contains "Windows Photo Viewer cant open this picture because file appears to be damaged, corrupted or is too large."
Client Side COde
WriteableBitmap wb = new WriteableBitmap(bitmapImage);
byte[] bb = ToByteArray(wb);
public byte[] ToByteArray(WriteableBitmap bmp)
{
int[] p = bmp.Pixels;
int len = p.Length * 4;
byte[] result = new byte[len]; // ARGB
Buffer.BlockCopy(p, 0, result, 0, len);
return result;
}
WebService Code
#WebMethod(operationName = "saveImage")
public Boolean saveImage(#WebParam(name = "img")
byte[] img, #WebParam(name = "path")
String path) {
try{
FileOutputStream fos = new FileOutputStream("C:\\Users\\TheIntersect\\Desktop\\sharp_serializer_dll\\saved.jpg");
fos.write(img);
fos.close();
return true;
}
catch(Exception e){
return false;
}
}
I found my answer on forums.silverlight.net
It is very interesting when i try to call ReadFully(Stream) just after the Stream definition it works but when i call 10 lines of code later it returns all 0.
FUnction
public static byte[] ReadFully(Stream input)
{
byte[] buffer = new byte[input.Length];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
Fail Code
using (Stream str = opd.File.OpenRead())
{
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(str);
image.Tag = bitmapImage.UriSource.ToString();
image.Source = bitmapImage;
image.Width = width;
image.Height = height;
image.Stretch = Stretch.Uniform;
container.Child = image;
rtb.Selection.Insert(container);
ServiceReference1.webWordWebServiceClient s = new ServiceReference1.webWordWebServiceClient();
byte[] bb = ReadFully(str);
s.saveImageCompleted += new EventHandler<ServiceReference1.saveImageCompletedEventArgs>(s_saveImageCompleted);
s.saveImageAsync(bb, "gungorrrr");
}
Successfull Code
using (Stream str = opd.File.OpenRead())
{
byte[] bb = ReadFully(str);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(str);
image.Tag = bitmapImage.UriSource.ToString();
image.Source = bitmapImage;
image.Width = width;
image.Height = height;
image.Stretch = Stretch.Uniform;
container.Child = image;
rtb.Selection.Insert(container);
ServiceReference1.webWordWebServiceClient s = new ServiceReference1.webWordWebServiceClient();
(bitmapImage);
s.saveImageCompleted += new EventHandler<ServiceReference1.saveImageCompletedEventArgs>(s_saveImageCompleted);
s.saveImageAsync(bb, "gungorrrr");
}
Link: http://forums.silverlight.net/forums/p/234126/576070.aspx#576070