Home > Android > Android: Saving a sound file to SD from resource and setting as ringtone

Android: Saving a sound file to SD from resource and setting as ringtone

January 26th, 2010 mat Leave a comment Go to comments

Quite a few people have been asking how to save a file to the SD card in order to register it as a ringtone. The following example creates a function that will save a resource to the SD card (ie: from R.raw.soundfile to /sdcard/media/audio/ringtones/soundfile.wav) and register it as a ringtone.

I have split this example into two parts, the first part goes through the code a section at a time with a brief explanation of what it does, the second half is just the code that you can copy and paste and then edit to your hearts content.

Parts

We first setup our function to return a boolean depicting if we have failed or if we are successful. We accept in an integer which corresponds to the raw sound file.

public boolean saveas(int ressound){

}

So this function would be called in the following fashion:

saveas(R.raw.soundfile);

or utilising its boolean return:

if (saveas(R.raw.soundfile)){
// Code if successful
}
else
{
// Code if unsuccessful
}

The following chunk of code creates an inputstream from the raw sound resource and loads it into a buffer. We add in the mandatory try/catch clause around these operations and return false if an exception is raised (to indicate failure to the rest of our program and to prevent trying to continue act upon this sound).

 byte[] buffer=null;
 InputStream fIn = getBaseContext().getResources().openRawResource(ressound);
 int size=0;

 try {
  size = fIn.available();
  buffer = new byte[size];
  fIn.read(buffer);
  fIn.close();
 } catch (IOException e) {
  // TODO Auto-generated catch block
  return false;
 }

The following saves the buffer to a file on the SD card. It first ensures the folder exists and if not it is created. Then as before the writing operations are surrounded with try/catches

 String path="/sdcard/media/audio/ringtones/";
 String filename="examplefile"+".wav";

 boolean exists = (new File(path)).exists();
 if (!exists){new File(path).mkdirs();}

 FileOutputStream save;
 try {
  save = new FileOutputStream(path+filename);
  save.write(buffer);
  save.flush();
  save.close();
 } catch (FileNotFoundException e) {
  // TODO Auto-generated catch block
  return false;
 } catch (IOException e) {
  // TODO Auto-generated catch block
  return false;
 }

The following code sends an intent to tell the Media Scanner that we have added a new file, and sets up its properties in the media database:

 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+path+filename)));

 File k = new File(path, filename);

 ContentValues values = new ContentValues();
 values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
 values.put(MediaStore.MediaColumns.TITLE, "exampletitle");
 values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/wav");
 values.put(MediaStore.Audio.Media.ARTIST, "cssounds ");
 values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
 values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
 values.put(MediaStore.Audio.Media.IS_ALARM, true);
 values.put(MediaStore.Audio.Media.IS_MUSIC, false);

 //Insert it into the database
 this.getContentResolver().insert(MediaStore.Audio.Media.getContentUriForPath(k.getAbsolutePath()), values);

Final code
Putting all this code together gives us our final functions:

public boolean saveas(int ressound){
 byte[] buffer=null;
 InputStream fIn = getBaseContext().getResources().openRawResource(ressound);
 int size=0;

 try {
  size = fIn.available();
  buffer = new byte[size];
  fIn.read(buffer);
  fIn.close();
 } catch (IOException e) {
  // TODO Auto-generated catch block
  return false;
 }

 String path="/sdcard/media/audio/ringtones/";
 String filename="examplefile"+".wav";

 boolean exists = (new File(path)).exists();
 if (!exists){new File(path).mkdirs();}

 FileOutputStream save;
 try {
  save = new FileOutputStream(path+filename);
  save.write(buffer);
  save.flush();
  save.close();
 } catch (FileNotFoundException e) {
  // TODO Auto-generated catch block
  return false;
 } catch (IOException e) {
  // TODO Auto-generated catch block
  return false;
 }    

 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+path+filename)));

 File k = new File(path, filename);

 ContentValues values = new ContentValues();
 values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
 values.put(MediaStore.MediaColumns.TITLE, "exampletitle");
 values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/wav");
 values.put(MediaStore.Audio.Media.ARTIST, "cssounds ");
 values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
 values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
 values.put(MediaStore.Audio.Media.IS_ALARM, true);
 values.put(MediaStore.Audio.Media.IS_MUSIC, false);

 //Insert it into the database
 this.getContentResolver().insert(MediaStore.Audio.Media.getContentUriForPath(k.getAbsolutePath()), values);

 return true;
}

Comments, suggestions etc. are always welcome. Hope this has been helpful.


Bookmark and Share
Categories: Android Tags: , , ,
  1. Steve
    January 27th, 2010 at 22:33 | #1

    This is great I have this part working. Can you explain more about the context menu and how it knows what file per button?

  2. January 27th, 2010 at 22:42 | #2

    Ok sure that’s a good idea, I’ll write up a example tomorrow on using context menus.

  3. Steve
    January 27th, 2010 at 23:21 | #3

    Thanks!

    I expanded your saveas(); to include a string used for the soundname that can be used later in the code

    public boolean saveas(int ressound, String soundname){

    Is it a good idea to do that?

  4. January 27th, 2010 at 23:26 | #4

    Yes that’s a good idea. In my application I created two arrays one for the sound files and one for the names of the sound files. Then I simply passed my function an integer which corresponded to the file in one array and the matching text in the other array. This meant that I only needed to change the array’s if I were to change all the sound files.

  5. G
    February 12th, 2010 at 17:21 | #5

    Using this code, there seem to be two issues: (1) that the file appears as a ringtone, but with the filename and not the title as its menu title. (2) that i can’t seem to categorize it as a notification tone and not a ring tone. Any clues?

  6. G
    February 12th, 2010 at 18:37 | #6

    Looking into it a bit further, it seems that what really happens is that the MediaStore entry gets changed and erases the Title/Artist that is put in there and replaces it with /. Is anyone else experiencing this?

  7. February 13th, 2010 at 00:10 | #7

    That seems very weird I’ll do some testing soon to see if I can reproduce the problem,

  8. Chris
    February 26th, 2010 at 16:56 | #8

    just curious.. did you put all of this into a OnLongClick? if not how was the user able to select to save as ringtone?

  9. Jake
    March 11th, 2010 at 17:58 | #9

    Hi, im having problems getting this to work, it wont seem to copy the raw sound file to the sd card, I have tried adding the storage permissions but this hasnt helped. my raw files are either an .ogg or an .mp3 but this should still work? Thanks.

  10. Wildheart Baby
    April 11th, 2010 at 21:00 | #10

    I’d also like to know how I can utilise this code with an on long click button press, how would I implement it.

  11. April 13th, 2010 at 16:34 | #11

    @Chris, yes this was implemented onlong click where the can choose between a ringtone and a notification

    @Jake, It’s hard to help without knowing what the error is, can you check the logs (adb logcat) when the error occurs?

    @Wildheart Baby, you can override the default onCreateContextMenu in your activity and create a context menu and override the onContextItemSelected which then calls a function what to do based on which view the press originated (differentiate by the view’s ID). I will add a short write up of this soon.

  12. April 14th, 2010 at 11:48 | #12
  13. May 11th, 2010 at 02:38 | #13

    It would be really nice if you could link the context menu guide and the save files to sd card guide.

    I am having trouble with my soundboard saving files…

  14. Chris
    May 30th, 2010 at 04:46 | #14

    hey matt..
    so we have a junk load of soundboards on the market now… but one main issue is after you save the sound file for notification or ringtone, if the phone loses power or something, then you have to re-save the sounds… our save code resembles yours very muhc, I was woundering if you’ve had this issue at all with yours?

  15. Colin
    July 2nd, 2010 at 03:26 | #15

    Great stuff here, Matt. I really appreciate the info and the presentation.

    I have a question, though, and it might be super duper noobish.. which would equate to my level of Java skills pretty accurately. *sigh*

    Anyway here goes: I’ve got the onlongclick button working correctly, and I’ve implemented this code as well, I think correctly. When I run the app in a virtual machine, however, I get a force close when I try to click on my “save as ringtone” button in my onlongclick menu. is this merely because I am using a virtual machine without a space to store the sound file or is there something I’ve neglected in the code itself?

    I’m not sure if that made perfect sense, so I apologize if it was unclear or missing details.

    Again, this is a huge help for me as a Java beginner!

  16. July 2nd, 2010 at 10:08 | #16

    Well to be completely sure what is causing your FC you should check logcat, but I agree with you if it tries to save to a place that doesnt exist, it will def force close. Please feel free to contact me via email in future as I will reply quicker

    android at stealthcopter.com

  17. July 8th, 2010 at 01:19 | #17

    Thank you so much!
    I have been struggling with how to play the music from my apk for 2 weeks! I’m using Processing with MediaPlayer and I was stumped; but a combination of your code and createInput() finally worked!

  18. udik
    July 22nd, 2010 at 17:19 | #18

    great help thanks!!

    Quick question – it works perfect for ringtones, but for notifications I get exception when I try to invoke the Ringtone picker and click on this newly added entry (or any entry) – here is the exception, appriciate much any idea…

    07-22 15:51:03.241: ERROR/AndroidRuntime(316): Uncaught handler: thread main exiting due to uncaught exception
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): android.database.CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.database.AbstractCursor.checkPosition(AbstractCursor.java:580)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:172)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:41)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.database.CursorWrapper.getString(CursorWrapper.java:135)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at com.android.internal.database.SortCursor.getString(SortCursor.java:205)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.media.RingtoneManager.getUriFromCursor(RingtoneManager.java:403)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.media.RingtoneManager.getRingtoneUri(RingtoneManager.java:399)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.media.RingtoneManager.getRingtone(RingtoneManager.java:382)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at com.android.internal.app.RingtonePickerActivity.run(RingtonePickerActivity.java:307)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.os.Handler.handleCallback(Handler.java:587)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.os.Handler.dispatchMessage(Handler.java:92)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.os.Looper.loop(Looper.java:123)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.app.ActivityThread.main(ActivityThread.java:4203)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at java.lang.reflect.Method.invokeNative(Native Method)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at java.lang.reflect.Method.invoke(Method.java:521)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:549)
    07-22 15:51:03.271: ERROR/AndroidRuntime(316): at dalvik.system.NativeStart.main(Native Method)

  19. udik
    July 22nd, 2010 at 20:02 | #19

    Never mind, need to add the following for notifcations:

    String path=”/sdcard/media/audio/notifications/”;

  1. January 27th, 2010 at 13:38 | #1
// unused langs // // // //