Objectes multimèdia

Un dels grans avantatges dels dispositius mòbils és que l’usuari té al seu abast una càmera de fotos i vídeo i una gravadora d’àudio en qualsevol moment, cosa que ha disparat el nombre de captura d’imatges i àudio per part dels usuaris, així com les aplicacions que permeten publicar instantàniament aquesta informació, compartir-la amb d’altres usuaris o emmagatzemar-la per donar-li ús posteriorment. Per treure profit d’un dispositiu mòbil cal saber, doncs, accedir a aquests accessoris, engegar-los i accedir als continguts gravats per reproduir-los o fer-ne un altre ús.

També és molt important saber accedir als continguts emmagatzemats al dispositiu per posar-los a l’abast de l’usuari, com ara mitjançant una galeria d’imatges, i distingir les diferents possibilitats d’emmagatzemament que ens ofereixen els dispositius mòbils (memòria interna, targetes de memòria externa…).

Finalment, al món multimèdia hi ha nombrosos formats per desar imatges, àudio i vídeo, i cal conèixer els més importants i saber controlar les aplicacions perquè generin els continguts multimèdia en diferents formats i amb diferents qualitats en funció del seu ús previst.

Com veureu, de tot això s’encarreguen un conjunt d’objectes (des del punt de vista de la programació orientada a objectes) que faciliten molt el desenvolupament d’aquest tipus d’aplicacions.

Ús de la càmera

Un dels accessoris o objectes multimèdia més importants als dispositius mòbils és la càmera fotogràfica. Més enllà de fer fotos amb les aplicacions estàndard, pot ser molt interessant que les vostres aplicacions permetin fer fotos directament i fer-les servir, com per exemple per fer fotos alhora que enregistreu una ruta amb GPS, o per fer un receptari de cuina amb la recepta i la foto del plat, o qualsevol altra aplicació que pugueu pensar.

Hi ha dos procediments per utilitzar la càmera a les nostres aplicacions: engegar l’aplicació estàndard de la càmera o accedir directament al dispositiu.

Accés a l'aplicació estàndard de la càmera de fotos

Podeu descarregar el codi corresponent a aquesta activitat en l’annex anomenat “Aplicació càmera de fotos” de la secció “Annexos”.

La manera més senzilla de fer una foto des d’una aplicació Android és engegant l’aplicació de càmera de fotos que tingui instal·lada el dispositiu. Lògicament, en contrapartida, la vostra aplicació tindrà menys control sobre el procés i el resultat de l’operació, però en molts casos només us caldrà fer una foto i emmagatzemar-la.

Abans de continuar, tingueu present que en general les fotos i d’altres captures s’emmagatzemen a la memòria externa (generalment, la targeta SD) del dispositiu. De fet, al principi l’aplicació estàndard per fer servir la càmera de fotos no funcionava si no hi havia una targeta SD al dispositiu. Per comprovar que el vostre emulador tingui configurada una targeta SD, heu d’obrir Tools/Android/AVD Manager, clicar a la icona d’edició i comprovar que tingui algun valor al camp SD Card (heu de mostrar les opcions avançades), com per exemple 200 (que són megabytes de capacitat de la targeta virtual), com es veu a la figura.

Figura Emulador Android amb targeta SD de 200 MB

Hem d’afegir el permís <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> al nostre AndroidManifest.xml. Aquest permís ens permet llegir i escriure a la memòria externa del dispositiu.

Els recursos mipmap són utilitzats per definir les icones de l’aplicació. En aquest cas, Google ens recomana usar aquests recursos per damunt dels drawable, ja que ens permeten treballar amb imatges de diferent resolució de la que realment té el dispositiu. Per exemple, en un dispositiu xhdpi, fent ús d’un mipmap aquest podria utilitzar una imatge de major resolució (per exemple una imatge xxhdpi) per la icona del llançador d’aplicacions.

El layout per a aquesta aplicació és molt senzill: només es necessita un botó per indicar que es vol fer la foto i un ImageView per visualitzar la foto que s’ha fet. Tot seguit podeu veure el contingut del layout, en el qual l’única propietat destacable és la propietat onClick del botó que té de valor fesFoto, que serà el mètode responsable d’engegar l’aplicació de la càmera:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:gravity="bottom"
  6. android:orientation="vertical" >
  7.  
  8. <ImageView
  9. android:id="@+id/imageView1"
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"
  12. android:layout_weight="5"
  13. android:src="@mipmap/ic_launcher" />
  14.  
  15. <Button
  16. android:id="@+id/button1"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:onClick="fesFoto"
  20. android:text="Foto" />
  21.  
  22. </LinearLayout>

Per iniciar una altra aplicació a Android cal crear un Intent i començar una nova activitat amb startActivityForResult(), per tal que puguem recollir el resultat de l’activitat mitjançant el mètode onActivityResult(). Per saber quina és l’activitat que ha retornat el resultat cal assignar-li un número qualsevol que la identifiqui, que en el codi següent hem associat a la variable APP_CAMERA, creada amb la finalitat d’emagatzemar-lo:

  1. // Número que identifica l'activitat de l'aplicació de fotos
  2. private static final int APP_CAMERA = 0;
  3.  
  4. // Identificador de la imatge que crearà l'aplicació de fotos
  5. private Uri identificadorImatge;
  6.  
  7. public void fesFoto(View view) {
  8. // Es crea l'intent per l'aplicació de fotos
  9. Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
  10. // Es crea un nou fitxer a l'emmagatzematge extern i se li passa a l'intent
  11. File foto = new File(Environment.getExternalStorageDirectory(), "Foto.jpg");
  12. intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(foto));
  13. // Es desa l'identificador de la imatge per recuperar-la quan estigui feta
  14. identificadorImatge = Uri.fromFile(foto);
  15. // S'engega l'activitat
  16. startActivityForResult(intent, APP_CAMERA);
  17. }

Abans d’iniciar l’aplicació estàndard de captura d’imatges es crea un nou fitxer a l’emmagatzemament extern (típicament, la targeta de memòria SD) i se’l passa a l’intent per tal que sàpiga on ha de desar la imatge que prengui. Finalment, s’executa l’activitat, que iniciarà l’aplicació de càmera fotogràfica per defecte (definida per l’usuari de cada dispositiu).

El segon pas, un cop feta la foto, és recollir el resultat de l’activitat que heu iniciat. En aquest cas només es vol mostrar la imatge que s’ha fet, però es podrien dur a terme altres operacions. També es mostrarà un Toast amb el nom amb el qual s’ha desat la imatge, si és el cas.

Totes aquestes operacions es porten a terme dins del mètode onActivityResult(), que s’executa cada cop que finalitza alguna activitat iniciada per la vostra aplicació. Com que una aplicació pot executar moltes activitats, cal fer servir un codi per distingir-les; en aquest cas és el paràmetre requestCode que coincideix amb el valor APP_CAMERA que hem passat a l’activitat en engegar-la. Distingir quina activitat ha finalitzat és l’objectiu del switch que veureu al codi d’exemple.

Tanmateix, l’activitat pot haver funcionat bé o pot haver fallat per diferents causes; per aquest motiu hi ha l’if, que comprova que el resultat (paràmetre resultCode) sigui RESULT_OK. L’altre valor possible per al resultCode d’una activitat és RESULT_CANCELED quan l’aplicació falla però tenim l’opció de definir altres codis personalitzats.

Un cop comprovat això, el codi accedeix a la imatge mitjançant el ContentResolver, que dóna accés als continguts del dispositiu, en aquest cas la foto que ha fet la càmera. Com que la càrrega de la imatge pot fallar, s’ha de fer dins d’un try… catch per tal de tractar les possibles excepcions. A més de la imatge, es mostra un toast amb el nom del fitxer on s’ha guardat la imatge o bé un missatge d’error si la càrrega falla.

  1. @Override
  2. public void onActivityResult(int requestCode, int resultCode, Intent data) {
  3. // Primer cridem al mètode d'Activity perquè faci la seva tasca
  4. super.onActivityResult(requestCode, resultCode, data);
  5. switch (requestCode) {
  6. case APP_CAMERA:
  7. if (resultCode == Activity.RESULT_OK) {
  8. /* El ContentResolver dóna accés als continguts
  9.   (la imatge emmagatzemada en aquest cas)*/
  10. ContentResolver contRes = getContentResolver();
  11. // Cal indicar que el contingut del fitxer ha canviat
  12. contRes.notifyChange(identificadorImatge, null);
  13. /* Accedeix a l'ImageView i hi carrega la foto que ha fet la
  14.   càmera */
  15. ImageView imageView = (ImageView) findViewById(R.id.imageView1);
  16. Bitmap bitmap;
  17. /* Com que la càrrega de la imatge pot fallar, cal tractar
  18.   les possibles excepcions*/
  19. try {
  20.  
  21. bitmap = android.provider.MediaStore.Images.Media
  22. .getBitmap(contRes, identificadorImatge);
  23.  
  24. /* Reduïm la imatge per no tenir problemes de visualització.
  25.   Calculem l'alçada per mantenir la proporció amb una amplada de 1080 píxels*/
  26. int alt = (int) (bitmap.getHeight() * 1080 / bitmap.getWidth());
  27. Bitmap reduit = Bitmap.createScaledBitmap(bitmap, 1080, alt, true);
  28.  
  29. imageView.setImageBitmap(reduit);
  30.  
  31. } catch (Exception e) {
  32. Toast.makeText(this, "No es pot carregar la imatge" +
  33. identificadorImatge.toString(),
  34. Toast.LENGTH_SHORT).show();
  35. }
  36. }
  37. }
  38. }
  39. }

Ara ja podeu executar l’aplicació i veure el seu funcionament: en clicar el botó s’engega l’aplicació per defecte per fer fotos, i aleshores la foto s’emmagatzema al dispositiu. A la figura teniu una captura de l’aplicació quan es mostra la foto realitzada.

La carpeta /storage/sdcard/ del dispositiu Android correspon a la targeta de memòria SD.

Figura Aplicació que fa fotos mitjançant l’aplicació estàndard

Accés directe a la càmera de fotos

De vegades les aplicacions requereixen un control més estret sobre el procés de captura d’imatges fotogràfiques, com per exemple obtenir-ne una previsualització en la mateixa aplicació, emmagatzemar les fotos directament, o fins i tot convertir-les a un determinat format. Per tal de veure com es pot dur a terme tot això, la següent aplicació accedirà directament a la càmera, integrarà la previsualització al seu layout principal i convertirà els píxels capturats al format desitjat per l’usuari, ja sigui JPEG o PNG, abans de desar la imatge.

Com que aquesta aplicació fa servir directament la càmera, que és un dispositiu important pel que fa a la privacitat de l’usuari (ningú no vol que una aplicació faci fotos sense el seu permís), en primer lloc cal donar permís a l’aplicació perquè pugui accedir a la càmera i perquè pugui llegir i escriure a la memòria externa. Creeu un nou projecte d’Android (anomenat Camera, per exemple) i obriu el seu fitxer AndroidManifest.xml per afegir el següent tot just abans de l’etiqueta application:

  1. <uses-permission android:name="android.permission.CAMERA"/>
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Aquesta aplicació s’ha d’executar a un emulador amb targeta SD instal·lada per poder desar les fotos.

Podeu descarregar el codi corresponent a aquesta activitat en l’annex anomenat “Accés directe a la càmera de fotos” de la secció “Annexos”.

Començareu per fer una aplicació que desi les fotos en format JPEG, i més endavant afegirem un botó per desar-les en format PNG. El layout és molt senzill, amb una àrea gran de previsualització i, a sota, un botó per fer la foto i un ImageView petit per veure la foto que s’ha fet.

La previsualització es fa mitjançant un control nou anomenat SurfaceView, dissenyat per a aquesta mena de tasques, i que podeu veure al codi del layout. Pel que fa a les seves propietats, és molt semblant a un ImageView. El botó té associa el mètode onClickFoto() a la seva propietat onClick.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical" >
  6.  
  7. <SurfaceView
  8. android:id="@+id/surfaceview"
  9. android:layout_width="fill_parent"
  10. android:layout_height="396dp"
  11. android:layout_weight="0.88" />
  12.  
  13. <LinearLayout
  14. android:id="@+id/linearLayout1"
  15. android:layout_width="match_parent"
  16. android:layout_height="wrap_content"
  17. android:layout_weight="0.88" >
  18.  
  19. <Button
  20. android:id="@+id/button1"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:onClick="onClickFoto"
  24. android:text="FOTO" />
  25.  
  26. <ImageView
  27. android:id="@+id/miniatura"
  28. android:layout_width="fill_parent"
  29. android:layout_height="fill_parent"
  30. android:scaleType="fitCenter"
  31. android:src="@mipmap/ic_launcher" />
  32.  
  33. </LinearLayout>
  34.  
  35. </LinearLayout>

Fet això, ja es pot començar a editar el codi de l’activitat de l’aplicació. En primer lloc, perquè l’aplicació sigui capaç de mostrar la previsualització de la càmera pel SurfaceView, la seva activitat principal ha d’implementar la interfície SurfaceHolder.Callback. Per fer-ho, heu de canviar la declaració de la vostra activitat tal com segueix:

  1. public class MainActivity extends ActionBarActivity implements SurfaceHolder.Callback {

Afegiu els imports que siguin necessaris. Veureu que l’Android Studio assenyala l’error CameraActivity degut a que, per tal que l’activitat implementi SurfaceHolder.Callback, ha d’incloure els mètodes surfaceChanged(), surfaceCreated() i surfaceDestroyed(). Feu clic sobre la línia que conté l’error i us apareixerà a l’esquerra un requadre amb diverses opcions disponibles, i seleccioneu l’opció Implement methods, amb la qual cosa s’afegiran els mètodes que faltaven:

  1. @Override
  2. public void surfaceCreated(SurfaceHolder holder) {
  3.  
  4. }
  5.  
  6. @Override
  7. public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  8.  
  9. }
  10.  
  11. @Override
  12. public void surfaceDestroyed(SurfaceHolder holder) {
  13.  
  14. }

La funció d’aquests mètodes i els seus paràmetres és donar suport a la previsualització de la càmera de fotos. Són cridats automàticament quan s’executa l’aplicació. Més concretament:

  • surfaceCreated() és cridat quan es crea la superfície de previsualització (és a dir, quan es crea l’activitat de l’aplicació) i rep l’activitat a la qual pertany (que és un SurfaceHolder, és a dir, que conté una superfície de previsualització). El que ha de fer és activar la càmera perquè comenci a rebre imatges.
  • surfaceChanged() és cridat quan la superfície de previsualització canvia de mida o format, per adaptar la previsualització a la seva mida real. A més del SurfaceHolder, rep el format de píxel i l’amplada i alçada de la superfície de previsualització en píxels. En aquest mètode s’ha de dir a la càmera la mida de previsualització que es necessita. Aquest mètode és cridat quan s’inicia l’aplicació, tot seguit de surfaceCreated().
  • surfaceDestroyed()és cridat quan la superfície de previsualització s’ha de destruir, i el que ha de fer en aquest cas és desconnectar la càmera.

Abans d’omplir aquests mètodes, s’ha de començar pel principi i afegir un atribut de tipus Camera per accedir a la càmera de fotos i inicialitzar el SurfaceView i les crides d’actualització de la vista prèvia. Editeu el començament de l’activitat del vostre projecte per afegir l’atribut i dur a terme la inicialització:

  1. // Aquest atribut permet accedir a la càmera de fotos
  2. Camera camera;
  3.  
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_main);
  8.  
  9. // Accedeix al SurfaceView i d'aquest al SurfaceHolder per activar les
  10. // actualitzacions de la vista prèvia (addCallback)
  11. SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceview);
  12. SurfaceHolder surfaceHolder = surfaceView.getHolder();
  13. surfaceHolder.addCallback(this);
  14. }

No us oblideu d’afegir els imports necessaris si no ho ha fet automàticament l’Android Studio. Fet això, quan s’engegui l’aplicació es cridaran els tres mètodes de SurfaceHolder.Callback que heu creat abans. En primer lloc cal que editeu el surfaceCreated() per fer que quan es creï la superfície de previsualització s’activi la càmera:

  1. @Override
  2. public void surfaceCreated(SurfaceHolder holder) {
  3. camera = Camera.open();
  4. }

Per tal que la superfície de previsualització i la càmera es posin d’acord pel que fa a la mida de les imatges de la vista prèvia, s’ha d’editar el surfaceChanged() per ajustar l’orientació del SurfaceView i la càmera; fixeu-vos que hem reanomenat els paràmetres perquè siguin més entenedors; vosaltres també haureu de reanomenar-los. Un cop fet això, s’activa la vista prèvia. Com que aquesta operació pot fallar, s’ha d’executar dins d’un try … catch per capturar les possibles excepcions.

  1. @Override
  2. public void surfaceChanged(SurfaceHolder holder, int format, int ample, int alt) {
  3. try {
  4. ficarOrientacioCamera(this, Camera.CameraInfo.CAMERA_FACING_BACK, camera);
  5.  
  6. camera.setPreviewDisplay(holder);
  7. } catch (Exception e) {
  8. Toast.makeText(this, "Error accedint a la càmera, causa:" + e.toString(),
  9. Toast.LENGTH_LONG).show();
  10. e.printStackTrace();
  11. }
  12. camera.startPreview();
  13. }

El mètode ficarOrientacioCamera ens permetrà calcular i aplicar l’orientació que tindrà la previsualització. Així, si tenim el mòbil en disposició vertical (mode portrait), la previsualització s’haurà de voltejar 90° ja que inicialment estarà preparada per rebre la informació en disposició horitzontal (mode landscape, 0°). Calculem els graus de diferència i els apliquem amb el mètode setDisplayOrientation. Tot seguit podeu veure el codi del mètode:

  1. public static void ficarOrientacioCamera(Activity activity, int cameraId, android.hardware.Camera camera) {
  2. Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
  3. Camera.getCameraInfo(cameraId, info);
  4. int rotacio = activity.getWindowManager().getDefaultDisplay().getRotation();
  5. int graus = 0;
  6.  
  7. switch (rotacio) {
  8. case Surface.ROTATION_0:
  9. graus = 0;
  10. break;
  11. case Surface.ROTATION_90:
  12. graus = 90;
  13. break;
  14. case Surface.ROTATION_180:
  15. graus = 180;
  16. break;
  17. case Surface.ROTATION_270:
  18. graus = 270;
  19. break;
  20. }
  21.  
  22. int result;
  23. if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
  24. result = (info.orientation + graus) % 360;
  25. result = (360 - result) % 360; // compensar l'efecte mirall
  26. } else { // camera posterior
  27. result = (info.orientation - graus + 360) % 360;
  28. }
  29. camera.setDisplayOrientation(result);
  30. }

L’últim punt important és aturar la vista prèvia i alliberar la càmera quan es tanqui l’aplicació. Això s’ha de fer al mètode surfaceDestroyed():

  1. @Override
  2. public void surfaceDestroyed(SurfaceHolder holder) {
  3. camera.stopPreview();
  4. camera.release();
  5. }

Ja podeu provar d’executar l’aplicació, però encara no cliqueu el botó de Foto ja que encara no hem escrit el mètode onClickFoto() al qual està associat, i l’aplicació fallaria. El que ha de fer aquest mètode és molt senzill: només ha de dir-li a la càmera que faci la foto i desar-la on calgui. Però això, que és fàcil de dir, és una mica més complicat de fer en realitat perquè la càmera no fa les fotos immediatament, sinó que es pren el seu temps (ha d’enfocar, disparar i enviar els píxels de la imatge).

Per aquest motiu, l’ordre enviada a la càmera perquè faci la foto, takePicture(), s’executa immediatament i no retorna la foto (la definició del mètode és void), sinó que aquesta es rep més endavant en un objecte de tipus PictureCallback que hem de crear prèviament perquè rebi la foto i, en aquest cas, la desi.

Aquest comportament és el que s’anomena una crida asíncrona, en la qual els resultats es reben més tard, en un altre lloc. Aquestes crides són freqüents quan accedim a dispositius i xarxes.

Més concretament, takePicture() rep tres paràmetres que són tres objectes amb les següents finalitats:

  • Camera.ShutterCallback shutter: l’objecte que s’ha de cridar just quan la càmera fa la foto. Per defecte aquest objecte fa el so del “clic”; si li asssigneu null aleshores no farà aquest clic, i si hi afegiu el vostre codi podeu redefinir el seu comportament.
  • Camera.PictureCallback raw: rep la foto que ha fet la càmera sense comprimir. Pot ser útil per emmagatzemar la foto en altres formats que no siguin JPEG, com ara en PNG. També pot ser null.
  • Camera.PictureCallback jpeg: rep la foto comprimida en format JPEG, per emmagatzemar-la o fer-ne qualsevol altra operació. També pot ser null.

En aquest exemple treballareu amb els paràmetres shutter i jpeg, perquè el paràmetre raw és més complicat de fer servir. Creareu un shutter buit per tal de gestionar el clic i veure quin és el seu format.

Comenceu amb la primera versió de l’ onClickFoto() per anar veient pas per pas com afegir les diferents funcionalitats:

  1. public void onClickFoto(View v) {
  2. camera.takePicture(null, null, null);
  3. }

Si executeu la vostra aplicació amb aquesta versió del mètode, veureu que quan cliqueu el botó Foto l’aplicació queda com aturada. Això és perquè la càmera atura la previsualització quan fa una foto, i aleshores, tot i que fa la foto, ni es sent el clic, ni es recull la foto, ni es continua amb la vista prèvia.

Canvieu el contingut de l‘onClickFoto() per sentir el clic i veure un toast per comprovar el que està passant. Fixeu-vos que a la crida camera.takePicture() canvia el primer argument per tal que es faci servir el ShutterCallback:

  1. public void onClickFoto(View v) {
  2. // Amb aquest objecte s'activa el clic en fer la foto i es mostra
  3. // un toast per comprovar que s'ha activat
  4. Camera.ShutterCallback shutterCB = new Camera.ShutterCallback() {
  5. @Override
  6. public void onShutter() {
  7. Toast.makeText(getApplicationContext(), "S'ha disparat!",
  8. Toast.LENGTH_LONG).show();
  9. }
  10. };
  11.  
  12. camera.takePicture(shutterCB, null, null);
  13. }

Si proveu d’executar l’aplicació i cliqueu el botó, sentireu el clic i veureu el toast, però res més. Encara queda feina per fer.

La manera més senzilla d’emmagatzemar una foto és amb el PictureCallback jpeg, que dóna els píxels de la imatge i amb unes quantes instruccions es pot desar. A més, amb aquest objecte mostrareu la foto que s’ha pres al petit ImageView que hi ha a la vostra aplicació; i finalment també fareu servir aquest nou objecte per tornar a activar la vista prèvia de la càmera. El següent codi s’ha d’afegir a l‘onClickFoto(), després del ShutterCallback que heu escrit abans:

  1. Camera.PictureCallback jpegCB = new Camera.PictureCallback() {
  2. // Aquest mètode és cridat quan la foto està feta
  3. @Override
  4. public void onPictureTaken(byte[] data, Camera cam) {
  5. // A "data" arriben les dades de la foto
  6. if (data != null) {
  7. // Converteix els píxels en bitmap
  8. Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length);
  9. // Mostra la imatge a l'ImageView de l'aplicació
  10. ImageView miniatura = (ImageView) findViewById(R.id.miniatura);
  11. miniatura.setImageBitmap(bm);
  12.  
  13. //Crea el directori fotos en cas de no existir
  14. File directori = new File(Environment.getExternalStorageDirectory().toString() + "/fotos/");
  15. if (!directori.exists()) {
  16. directori.mkdir();
  17. }
  18.  
  19. /* Desa la imatge en format JPG */
  20. try {
  21. // Genera un nom únic per la imatge
  22. String nomFitx = directori.getAbsolutePath() + "/" + UUID.randomUUID().toString() + "-foto.jpg";
  23.  
  24. // Generem la sortida a partir del nom del fitxer
  25. FileOutputStream fos = new FileOutputStream(nomFitx);
  26. // Comprimeix la imatge com a JPG
  27. bm.compress(Bitmap.CompressFormat.JPEG, 85, fos);
  28. // Mostra el nom del fitxer on s'ha desat
  29. Toast.makeText(getApplicationContext(), nomFitx, Toast.LENGTH_SHORT).show();
  30. // Alliberem l'OutputStream
  31. fos.flush();
  32. fos.close();
  33.  
  34. } catch (Exception e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. // Torna a activar la vista prèvia de la càmera
  39. camera.startPreview();
  40. }
  41. };

Si ens fixem amb el codi del mètode onPictureTaken podem diferenciar els següents blocs de codi:

  1. // Converteix els píxels en bitmap
  2. Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length);
  3. // Mostra la imatge a l'ImageView de l'aplicació
  4. ImageView miniatura = (ImageView) findViewById(R.id.miniatura);
  5. miniatura.setImageBitmap(bm);

Amb aquest codi obtenim el Bitmap a partir de l’argument data del mètode, i posteriorment l’assignem al nostre ImageView. Amb aquestes instruccions ja tindrem la representació en pantalla de la imatge i disposarem del Bitmap per després guardar-lo a l’emmagatzemament extern.

  1. //Crea el directori fotos en cas de no existir
  2. File directori = new File(Environment.getExternalStorageDirectory().toString() + "/fotos/");
  3. if (!directori.exists()) {
  4. directori.mkdir();
  5. }
  6.  

Hem d’escollir a quin directori guardarem la imatge, amb les línies anteriors estem seleccionant el directori “fotos” que es trobarà a l’emmagatzemament extern per defecte (habitualment /storage/sdcard/).

El condicional comprovarà si no existeix aquest directori i, en cas així sigui, el crearà amb la comanda mkdir.

  1. /* Desa la imatge en format JPG */
  2. try {
  3. // Genera un nom únic per la imatge
  4. String nomFitx = directori.getAbsolutePath() + "/" + UUID.randomUUID().toString() + "-foto.jpg";
  5.  
  6. // Generem la sortida a partir del nom del fitxer
  7. FileOutputStream fos = new FileOutputStream(nomFitx);
  8. // Comprimeix la imatge com a JPG
  9. bm.compress(Bitmap.CompressFormat.JPEG, 85, fos);
  10. // Mostra el nom del fitxer on s'ha desat
  11. Toast.makeText(getApplicationContext(), nomFitx, Toast.LENGTH_SHORT).show();
  12. // Alliberem l'OutputStream
  13. fos.flush();
  14. fos.close();
  15.  
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }

El mètode Bitmap.compress() rep el format amb el qual es vol desar la imatge, la seva qualitat i el fitxer on es desarà. El paràmetre de la qualitat controla si la imatge s’emmagatzema amb una mida mínima i una qualitat mínima (si qualitat = 0), o amb una mida màxima i qualitat (qualitat = 100), o qualsevol valor intermedi.

Amb aquestes instruccions, el que esteu fent en primer lloc és obtenir un nom de fitxer únic (gràcies al mètode randomUUID()) que estigui ubicat al directori “fotos” que hem seleccionat al codi anterior. Tot seguit, es crea un fitxer de sortida (FileOutputStream fos) per poder desar la imatge. Amb Bitmap.compress() es comprimeix i es desa la imatge al fitxer, i finalment es mostra un toast per veure el nom del fitxer.

Finalment, fem un flush() i un close() de l‘OutputStream, és a dir, forcem l’escriptura de dades que puguin quedar al buffer, i tanquem i alliberem el recurs.

Ara que ja ho teniu tot preparat, heu de canviar la crida a camera.takePicture() perquè faci servir aquest nou objecte:

        camera.takePicture(shutterCB, null, jpegCB);

Si proveu d’executar l’aplicació i fer una foto, sentireu el clic i veureu els dos toast, amb la qual cosa s’haurà desat la vostra imatge. Podeu veure una captura de la pantalla a la figura.

Figura Aplicació de fotos

Per tal d’accedir a la imatge podeu obrir el monitor de l’emulador Android anant a Tools/Android/Android Device Monitor, on heu d’accedir a la pestanya File Explorer per tal de veure els fitxers del dispositiu virtual Android.

Aneu a la carpeta /storage/sdcard/fotos, tal com es veu a la figura. En aquesta carpeta hi trobareu les imatges que ha capturat la càmera. A la part superior de la finestra hi ha un botó amb un disquet i una fletxa al damunt que serveix per transferir un fitxer de l’emulador al vostre ordinador. Proveu de copiar alguna de les imatges per comprovar que la foto correspon a la que heu pres amb la càmera.

Figura Imatges capturades al sistema d’arxius de l’emulador

Per guardar la foto en format PNG només haurem de canviar el nom del fitxer i el Bitmap.CompressFormat; així, el codi del PictureCallback quedaria de la següent manera:

JPEG i PNG

Alguns formats d’imatge, com ara el JPEG, permeten desar amb més o menys qualitat les imatges. PNG sempre desa amb màxima qualitat i, per tant, el paràmetre qualitat s’ignora.

  1. Camera.PictureCallback jpegCB = new Camera.PictureCallback() {
  2. // Aquest mètode és cridat quan la foto està feta
  3. @Override
  4. public void onPictureTaken(byte[] data, Camera cam) {
  5. // A "data" arriben les dades de la foto
  6. if (data != null) {
  7. // Converteix els píxels en bitmap
  8. Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length);
  9. // Mostra la imatge a l'ImageView de l'aplicació
  10. ImageView miniatura = (ImageView) findViewById(R.id.miniatura);
  11. miniatura.setImageBitmap(bm);
  12.  
  13. //Crea el directori fotos en cas de no existir
  14. File directori = new File(Environment.getExternalStorageDirectory().toString() + "/fotos/");
  15. if (!directori.exists()) {
  16. directori.mkdir();
  17. }
  18.  
  19. /* Desa la imatge en format PNG */
  20. try {
  21. // Genera un nom únic per la imatge
  22. String nomFitx = directori.getAbsolutePath() + "/" + UUID.randomUUID().toString() + "-foto.png";
  23.  
  24. // Generem la sortida a partir del nom del fitxer
  25. FileOutputStream sort = new FileOutputStream(nomFitx);
  26. // Comprimeix la imatge com a JPG
  27. bm.compress(Bitmap.CompressFormat.PNG, 100, sort);
  28. // Mostra el nom del fitxer on s'ha desat
  29. Toast.makeText(getApplicationContext(), nomFitx, Toast.LENGTH_SHORT).show();
  30. // Allibera l'OutputStream
  31. sort.flush();
  32. sort.close();
  33.  
  34. } catch (Exception e) {
  35. e.printStackTrace();
  36. }
  37. }
  38.  
  39. // Torna a activar la vista prèvia de la càmera
  40. camera.startPreview();
  41. }
  42. };

Amb unes senzilles operacions heu vist com convertir dades multimèdia d’un format a un altre.

Mostrar imatges de la targeta de memòria

A banda de mostrar imatges que s’hagin afegit com a recursos de l’aplicació, Android permet visualitzar les imatges que hi ha emmagatzemades a la memòria del dispositiu, com per exemple a la targeta de memòria, que és on s’acostumen a emmagatzemar les imatges capturades amb la càmera de fotos o descarregades d’Internet.

Podeu descarregar el codi corresponent a aquesta activitat en l’annex anomenat “Imatges de la targeta de memòria” de la secció “Annexos”.

Així doncs, en aquest exemple veureu com es pot triar una imatge emmagatzemada a la targeta SD del dispositiu, que és on es guarden les fotografies fetes amb la càmera, per exemple. Comenceu un nou projecte Android. En primer lloc afegiu el permís <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> abans de l’etiqueta application del fitxer AndroidManifest.xml i prepareu la interfície de l’aplicació perquè inclogui un ImageView que us mostrarà la imatge.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical" >
  6.  
  7. <ImageView
  8. android:id="@+id/imageView1"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:src="@mipmap/ic_launcher" />
  12.  
  13. </LinearLayout>

Al mètode onCreate() de l’activitat s’hi han d’afegir les següents instruccions, que el que fan és engegar una nova activitat que permet triar una imatge. A més, es pot recollir el resultat d’aquesta activitat:

  1. Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  2. startActivityForResult(i, ACTIVITAT_SELECCIONAR_IMATGE);

Com veieu, es fa servir una constant per identificar l’activitat; cal declarar-la com a constant de la classe (és a dir, abans de l’onCreate()):

  1. private static final int ACTIVITAT_SELECCIONAR_IMATGE = 1;

El valor concret que li doneu a la constant no és important, mentre no assigneu el mateix valor a més d’una constant que es faci servir per engegar activitats noves.

Amb tot això, quan s’obre la vostra aplicació immediatament s’inicia una activitat que us deixa triar alguna foto de la targeta SD, tal com es veu a la figura.

Figura Activitat de selecció d’imatges

Un cop trieu una de les imatges, aquesta activitat es tancarà i retornarà la imatge seleccionada a la vostra activitat, però si ho proveu ara mateix veureu que en comptes de mostrar la imatge seleccionada segurament apareixerà la icona d’Android (imatge per defecte de l’ImageView). Per què? Doncs perquè no hem recollit el resultat de l’activitat. Per fer-ho, cal afegir-hi el següent mètode:

  1. protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
  2. super.onActivityResult(requestCode, resultCode, intent);
  3.  
  4. switch (requestCode) {
  5. case ACTIVITAT_SELECCIONAR_IMATGE:
  6. if (resultCode == RESULT_OK) {
  7. Uri seleccio = intent.getData();
  8. String[] columna = {MediaStore.Images.Media.DATA};
  9.  
  10. Cursor cursor = getContentResolver().query(seleccio, columna, null, null, null);
  11. cursor.moveToFirst();
  12.  
  13. int indexColumna = cursor.getColumnIndex(columna[0]);
  14. String rutaFitxer = cursor.getString(indexColumna);
  15. cursor.close();
  16.  
  17. Bitmap imatge = BitmapFactory.decodeFile(rutaFitxer);
  18. /* Reduïm la imatge per no tenir problemes de visualització.
  19.   Calculem l'alçada per mantenir la proporció amb una amplada de 1080 píxels
  20.   */
  21. int alt = (int) (imatge.getHeight() * 1080 / imatge.getWidth());
  22. Bitmap reduit = Bitmap.createScaledBitmap(imatge, 1080, alt, true);
  23.  
  24. // Afegim la imatge reduïda a l'Imageview
  25. ImageView imageView = (ImageView) findViewById(R.id.imageView1);
  26. imageView.setImageBitmap(reduit);
  27. }
  28. }
  29. }

Vegem pas a pas què fa aquest mètode per mostrar la imatge seleccionada. En primer lloc, és important el nom del mètode, onActivityResult(), perquè serà cridat quan alguna activitat engegada amb startActivityForResult() acabi. El mètode rep tres paràmetres: requestCode, que és el codi amb què heu iniciat l’activitat (la constant definida prèviament); resultCode, que és el codi d’èxit o error de l’activitat; i finalment, intent, que dóna accés a les dades de l’activitat.

En primer lloc es crida el mètode corresponent de la superclasse perquè faci les operacions bàsiques. Tot seguit, s’ha de mirar quin és el codi de l’activitat (perquè poden haver-se’n llançat vàries) amb el switch, i comprovar que l’activitat ha tornat amb èxit (que resultCode sigui RESULT_OK). Si no, us podria aparèixer algun missatge d’error.

Un cop comprovat això ja es pot començar a extreure la imatge. Penseu que l’activitat pren una estructura de dades complexa, una taula (seleccio) amb moltes columnes d’informació; n’extraiem la columna DATA, que és la que ens interessa. Tot seguit fem servir un cursor (una mena d’índex a la taula) per accedir a la posició que ens interessa (el primer element), i finalment llegim el seu contingut per obtenir el nom del fitxer d’imatge (rutaFitxer).

Només queda llegir i carregar la imatge a l’aplicació (variable imatge) i accedir a l’ImageView per tal que la mostri; abans, però, la reduirem per no tenir problemes de memòria que impedeixin la seva representació. Ara ja podreu veure la imatge quan la seleccioneu.

Enregistrar àudio

Els dispositius mòbils poden ser eines útils com a enregistradores de so, perquè estan equipats amb un micròfon i disposen d’espai per emmagatzemar so. Per tal de fer servir aquesta prestació, crearem una aplicació que, quan es premi un botó, començarà a enregistrar so pel micròfon del dispositiu; un cop fet això, permetrà reproduir el so per tal de comprovar que s’ha enregistrat el que es desitjava.

L’emulador d’Android no accedeix al micròfon, per la qual cosa si executeu aquesta aplicació a l’emulador es crearà un fitxer d’àudio vàlid però en silenci; cal executar-la, doncs, en un dispositiu per provar-la.

L’enregistrament de so a Android s’aconsegueix mitjançant un objecte MediaRecorder, que serveix per enregistrar àudio i vídeo d’una manera relativament senzilla. Un cop s’hagi desat el fitxer d’àudio, es reproduirà fent servir un objecte de tipus MediaPlayer, que és el complementari del MediaRecorder: permet reproduir àudio i vídeo a les aplicacions Android.

Podeu descarregar el codi corresponent a aquesta activitat en l’annex anomenat “Enregistrar àudio” de la secció “Annexos”.

El fitxer d’àudio es desarà a la carpeta compartida per música i so, de manera que qualsevol altra aplicació hi tingui accés. Així doncs, els permisos que requereix l’aplicació seran dos: accedir al micròfon i escriure fitxers a l’emmagatzemament extern. Creeu un nou projecte d’Android i afegiu els permisos següents al fitxer AndroidManifest.xml:

  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  2. <uses-permission android:name="android.permission.RECORD_AUDIO"/>

D’altra banda, la interfície d’aquesta aplicació és molt senzilla: només cal crear dos botons, un per iniciar o aturar la gravació i un altre per reproduir l’arxiu de so. El layout.xml ha de ser el següent:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  3. android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
  4. android:paddingRight="@dimen/activity_horizontal_margin"
  5. android:paddingTop="@dimen/activity_vertical_margin"
  6. android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
  7.  
  8. <ToggleButton
  9. android:id="@+id/botoGravar"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:layout_alignParentLeft="true"
  13. android:layout_alignParentTop="true"
  14. android:textOff="Gravar"
  15. android:textOn="Aturar"
  16. android:text="ToggleButton"
  17. android:onClick="onClickBotoGravar"/>
  18.  
  19. <Button
  20. android:id="@+id/botoReproduir"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:layout_alignParentTop="true"
  24. android:layout_marginLeft="15dp"
  25. android:layout_toRightOf="@+id/botoGravar"
  26. android:text="Reproduir"
  27. android:onClick="onClickBotoReproduir"/>
  28.  
  29. </RelativeLayout>

Per gravar fareu servir un ToggleButton, que té una llumeneta que s’encén quan el botó està “activat”. En aquest cas el fareu servir per indicar que s’està gravant. Com podeu veure, cada botó té associat un mètode diferent que s’executarà quan el cliqueu (propietat onClick).

El funcionament general de l’aplicació serà el següent: amb dues variables booleanes es controlarà si l’aplicació està gravant o no i si està reproduint o no l’arxiu d’àudio. També es necessiten atributs de l’activitat per recordar el nom del fitxer d’àudio i els objectes MediaRecorder i MediaPlayer que es faran servir. Al mètode onCreate() de l’activitat es definirà el nom del fitxer d’àudio. Per tant, el començament del codi de l’aplicació serà:

  1. private static String mNomFitxer = null;
  2.  
  3. private MediaRecorder mRecorder = null;
  4. private MediaPlayer mPlayer = null;
  5. private boolean mGravant = false;
  6. private boolean mReproduint = false;
  7.  
  8. @Override
  9. public void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12. // Generar el nom del fitxer
  13. mNomFitxer = Environment.getExternalStorageDirectory() + "/gravacio.3gp";
  14.  
  15. }

A la secció “Gravació en altres formats i codificacions” veurem com emmagatzemar el so en altres formats populars, com ara l’MP4.

Com podeu veure, l’extensió de l’arxiu d’àudio és 3gp, que és un format d’àudio i vídeo molt habitual per a dispositius mòbils. Quan es creï el MediaRecorder s’haurà d’especificar que el format sigui aquest.

La primera funcionalitat que afegirem és la gravació amb el micròfon. Quan l’usuari de l’aplicació clica el botó Gravar, es crida el mètode onClickBotoGravar() tal com heu vist al layout de l’aplicació. D’altra banda, aquest mètode comprova l’estat actual (gravant o no, amb l’atribut mGravant) i segons el seu valor atura la gravació (cridant el mètode aturaGravacio()) o l’engega amb el mètode comencaGravacio(). Finalment, canvia a l’altre estat (si estava gravant deixa de gravar i viceversa).

  1. public void onClickBotoGravar(View view) {
  2. if (mGravant) {
  3. aturaGravacio();
  4. } else {
  5. comencaGravacio();
  6. }
  7. // Canvia a l'altre estat
  8. mGravant = !mGravant;
  9. }

El mètode comencaGravacio() té com a tasques principals crear el MediaRecorder, configurar-lo perquè gravi el so del micròfon al fitxer i format desitjat, i finalment posar en marxa la gravació. Els paràmetres més importants que cal indicar al MediaRecorder són:

  • Font d’àudio amb el mètode setAudioSource(), en aquest cas el micròfon. Hi ha altres valors possibles, els més interessants dels quals són MediaRecorder.AudioSource.VOICE_UPLINK (gravació del so que envia l’usuari en una conversa telefònica) i MediaRecorder.AudioSource.VOICE_DOWNLINK (gravació del so que rep l’usuari en una conversa telefònica, és a dir, el que diu l’altre interlocutor).
  • Format del fitxer d’àudio que es gravarà amb el mètode setOutputFormat(), en aquest cas 3GP (MediaRecorder.OutputFormat.THREE_GPP). Altres valors interessants són MediaRecorder.OutputFormat.AAC_ADTS, un format d’alta qualitat sovint utilitzat per a l’àudio de pel·lícules, o MediaRecorder.OutputFormat.MPEG_4, per guardar l’àudio en format MP4, que és un format suportat per molts dispositius i programes.
  • Nom del fitxer d’àudio que es gravarà, mitjançant setOutputFile().
  • Codificació d’àudio, mitjançant setAudioEncoder(). En aquest cas es fa servir MediaRecorder.AudioEncoder.AMR_NB, però hi ha d’altres codificacions amb més qualitat, com ara MediaRecorder.AudioEncoder.AMR_WB, o bé MediaRecorder.AudioEncoder.AAC.

Codificació i compressió

No heu de confondre el format de fitxer multimèdia (que només indica de quina manera s’emmagatzema al disc), amb la codificació o compressió, que indica de quina manera es comprimeixen l’àudio i el vídeo per tal que no ocupi molta memòria. Molts formats (també anomenats contenidors) suporten diferents codificacions.

  1. private void comencaGravacio() {
  2. // Crea el MediaRecorder i especifica la font d'audio, el format
  3. // de sortida i el fitxer, i el codificador d'audio
  4. mRecorder = new MediaRecorder();
  5. mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
  6. mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
  7. mRecorder.setOutputFile(mNomFitxer);
  8. mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
  9.  
  10. // En enllestir la gravació poden sorgir problemes, per tant cal
  11. // preveure excepcions
  12. try {
  13. mRecorder.prepare();
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. }
  17.  
  18. // Si s'ha pogut disposar tot correctament, es comença a gravar
  19. mRecorder.start();
  20. }

D’altra banda, per aturar la gravació s’ha d’aturar el MediaRecorder i alliberar-lo perquè alliberi el micròfon i el deixi a l’abast d’altres aplicacions:

  1. private void aturaGravacio() {
  2. mRecorder.stop();
  3. mRecorder.release();
  4. mRecorder = null;
  5. }

Pel que fa a la reproducció del fitxer d’àudio que s’ha gravat, el mètode onClickBotoReproduir() és molt semblant a onClickBotoGravar(), ja que mira si ja s’està reproduint el fitxer o no, i segons sigui aquest valor atura o engega la reproducció.

  1. public void onClickBotoReproduir(View v) {
  2. if (mReproduint) {
  3. aturaReproduccio();
  4. } else {
  5. comencaReproduccio();
  6. }
  7. // Canvia a l'altre estat
  8. mReproduint = !mReproduint;
  9. }

Els següents dos mètodes fan la feina d’iniciar i aturar la reproducció del so. El primer, comencaReproduccio(), crea un MediaPlayer, especifica el fitxer multimèdia que farà servir amb setDataSource(), prepara el reproductor i el posa en marxa. Com que aquest procés pot fallar (per exemple, si el fitxer no està disponible), s’han d’encerclar les operacions amb un try… catch.

D’altra banda, per aturar la reproducció només cal alliberar el MediaPlayer, que és el que fa el mètode aturaReproduccio().

  1. private void comencaReproduccio() {
  2. mPlayer = new MediaPlayer();
  3. try {
  4. mPlayer.setDataSource(mNomFitxer);
  5. mPlayer.prepare();
  6. mPlayer.start();
  7. } catch (IOException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11.  
  12. private void aturaReproduccio() {
  13. mPlayer.release();
  14. mPlayer = null;
  15. }

Ja podeu executar l’aplicació i veure com grava el fitxer gravacio.3gp a la carpeta /storage/sdcard/. A la figura podeu veure una captura de pantalla de l’aplicació.

Figura Aplicació de gravació d’àudio en marxa

Gravació en altres formats i codificacions

Un cop feta l’aplicació per gravar àudio, és molt senzill modificar-la perquè els fitxers de sortida estiguin en altres formats i amb altres codificacions. Per exemple, imagineu que necessiteu fer una gravació amb més qualitat i format MP4. Només heu de fer tres canvis a l’aplicació de gravació d’àudio.

En primer lloc, el fitxer on es gravarà hauria de tenir l’extensió mp4. Editeu les línies corresponents a l‘onCreate() i feu el següent:

mNomFitxer= Environment.getExternalStorageDirectory() + "/gravacio.mp4";

A banda d’això, s’ha de canviar el format del fitxer i la codificació que s’utilitza (per fer-ne servir una amb més qualitat de so). Aneu al mètode comencaGravacio() i editeu-hi les línies següents per especificar el format MP4 i la codificació AAC:

  1. mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
  2. ....
  3. mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

Fet! Amb només tres línies podeu especificar fàcilment el format del fitxer d’àudio que es gravarà.

Gravació de vídeo

Com que els dispositius mòbils disposen de càmera i micròfon, també ofereixen la possibilitat de gravar vídeos. Com en el cas de la fotografia, és possible gravar vídeos fent servir l’aplicació de gravació que hi hagi instal·lada o bé programar directament tots els detalls per tal de previsualitzar el vídeo i indicar a la càmera que comenci a gravar. En aquest cas, farem servir l’aplicació de gravació de vídeo perquè acostuma a incloure moltes funcionalitats i els usuaris ja estan acostumats a fer-la servir.

El procediment per activar l’aplicació de gravació des de les vostres aplicacions és l’habitual: executar un intent amb el tipus de tasca que es necessita i recollir el seu resultat per veure si tot ha funcionat correctament; i, en aquest cas, s’ha gravat el vídeo o, altrament, si s’ha cancel·lat la gravació.

En el cas que s’hagi gravat el vídeo correctament, l’aplicació oferirà la possibilitat de reproduir-lo per tal que l’usuari pugui comprovar el seu contingut. Com és habitual, les vostres aplicacions podrien fer qualsevol altra operació amb el vídeo gravat.

Podeu descarregar el codi corresponent a aquesta activitat en l’annex anomenat “Gravació de vídeo” de la secció “Annexos”.

Creeu un nou projecte Android anomenat GravaVideo amb les opcions per defecte i editeu el seu layout perquè quedi així:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6.  
  7. <Button
  8. android:id="@+id/botoGravar"
  9. android:layout_width="fill_parent"
  10. android:layout_height="wrap_content"
  11. android:text="Gravar vídeo"
  12. android:onClick="onClickBotoGravar" />
  13.  
  14. <Button
  15. android:id="@+id/botoReproduir"
  16. android:layout_width="fill_parent"
  17. android:layout_height="wrap_content"
  18. android:text="Reproduir"
  19. android:enabled="false"
  20. android:onClick="onClickBotoReproduir" />
  21.  
  22. <VideoView
  23. android:id="@+id/visorVideo"
  24. android:layout_width="fill_parent"
  25. android:layout_height="wrap_content" />
  26. </LinearLayout>

Aquest projecte conté dos botons, un per indicar que s’iniciï l’aplicació de gravació de vídeo, i un altre per reproduir el vídeo quan es rebi. Aquest segon botó restarà desactivat (enabled=”FALSE”) fins que hi hagi un vídeo disponible, com es mostra a la figura (és allò que veuríeu si provéssiu d’executar l’aplicació en aquest moment).

Figura Aplicació de gravació i reproducció de vídeo

Haurem d’afegir el següent permís al fitxer AndroidManifest.xml per tal que l’aplicació tingui permís per llegir a l’emmagatzemament extern:

  1. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

El que ha de fer el botó de gravar és crear i executar un Intent que demani l’aplicació de gravació de vídeo que l’usuari tingui instal·lada per defecte.

Aleshores, el començament de l’aplicació haurà de ser com mostra el codi a continuació: es defineix una constant (INTENT_GRAVAR_VIDEO) per identificar l’intent que engegarà el gravador de vídeo (li podeu assignar un número qualsevol), i un Uri per rebre l’adreça del vídeo.

Un Uri és un Universal Resource Identifier, un nom que identifica un recurs, en aquest cas el vídeo gravat.

El mètode onCreate() és força estàndard: simplement activa el layout que heu creat abans. D’altra banda, el mètode onClickBotoGravar() només ha de crear l’intent que demana una captura de vídeo i llançar una activitat amb aquest intent. Com que se li passa la constant INTENT_GRAVAR_VIDEO, quan torni l’aplicació sabrà a què correspon el resultat rebut.

  1. public class MainActivity extends ActionBarActivity {
  2.  
  3. // Una constant per identificar l'intent de gravar vídeo
  4. final static int INTENT_GRAVAR_VIDEO = 1;
  5. // Aquí tornarà l'adreça del vídeo gravat
  6. Uri uriVideo = null;
  7.  
  8. @Override
  9. public void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12. }
  13.  
  14. public void onClickBotoGravar(View view) {
  15. // Es crea l'intent i es llança
  16. Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
  17. startActivityForResult(intent, INTENT_GRAVAR_VIDEO);
  18. }
  19. }

Quan l’intent que acabeu de llançar torna, es crida el mètode onActivityResult(), que rep tres paràmetres: requestCode, que és el codi amb el qual l’heu creat (INTENT_GRAVAR_VIDEO); resultCode, que indica si l’intent ha acabat bé (RESULT_OK) o ha estat cancel·lat per l’usuari (RESULT_CANCELED), i data, que torna les dades de l’intent, en aquest cas l’URI del vídeo gravat.

Amb aquesta informació, el mètode comprova que l’intent hagi acabat bé, que sigui l’intent de gravació de vídeo que s’ha engegat abans, i en cas afirmatiu mostra amb un toast l’URI del vídeo i activa el botó de reproducció (que estava desactivat per defecte); si no, mostra un missatge per indicar a l’usuari que la gravació s’ha cancel·lat.

  1. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  2. if (resultCode == RESULT_OK) {
  3. if (requestCode == INTENT_GRAVAR_VIDEO) {
  4.  
  5. uriVideo = data.getData();
  6. Toast.makeText(this,
  7. uriVideo.getPath(),
  8. Toast.LENGTH_LONG).show();
  9. // Activar el botó de reproduir vídeo perquè ja hi ha un vídeo
  10. Button botoReproduir = (Button) findViewById(R.id.botoReproduir);
  11. botoReproduir.setEnabled(true);
  12. }
  13. } else if (resultCode == RESULT_CANCELED) {
  14. uriVideo = null;
  15. Toast.makeText(this,
  16. "Gravació cancel·lada!",
  17. Toast.LENGTH_LONG).show();
  18. }
  19. }

Al resultCode d’una activitat també s’hi poden fer servir valors propis per indicar diferents resultats.

La segona funcionalitat de l’aplicació és mostrar el vídeo que s’ha gravat quan l’usuari premi el botó Reproduir, que s’ha activat quan l’aplicació ha gravat un vídeo amb èxit (perquè si aquest botó hagués estat disponible i l’usuari l’hagués clicat, s’hauria produït un error).

La funcionalitat del mètode que s’executa en clicar el botó de reproduir és molt senzilla: simplement mostra un toast amb l’URI del vídeo i tot seguit accedeix al VideoView, carrega el vídeo i comença la reproducció.

  1. public void onClickBotoReproduir(View view) {
  2. // Primer mostra un toast amb l'URI del vídeo
  3. Toast.makeText(this,
  4. "Reproduint: " + uriVideo.getPath(),
  5. Toast.LENGTH_LONG).show();
  6.  
  7. // Després accedeix al visor, carrega el vídeo i el reprodueix
  8. VideoView visorVideo = (VideoView) findViewById(R.id.visorVideo);
  9. visorVideo.setVideoURI(uriVideo);
  10. visorVideo.start();
  11. }

Un cop completada l’aplicació, podeu provar d’executar-la i comprovar el seu comportament. A la figura es pot veure la gravació de vídeo engegada (amb l’aplicació estàndard instal·lada per l’usuari del dispositiu).

Figura Aplicació de gravació de vídeo en el moment de reproduir la gravació

Un cop el vídeo s’ha gravat, la vostra aplicació us mostra l’URI del vídeo, que és un identificador simbòlic que permet accedir-hi, com es veu a la figura. No es tracta pas de l’adreça del fitxer de vídeo, que per defecte es gravarà a la carpeta /storage/sdcard/DCIM/Camera/ del dispositiu amb un nom com ara VID_20150101_121310.mp4, generat automàticament amb la data i l’hora.

Comproveu-ho fent servir el monitor de l’emulador: Tools/Android/Android Device Monitor.

Com podeu comprovar també, l’aplicació permet gravar més d’un vídeo; com que feu ús de l’aplicació estàndard de gravació, cada nou vídeo que graveu rep un nom diferent (amb la data i l’hora) i, com podeu veure a les toast que van sortint, una URI diferent per tal que l’aplicació els pugui distingir.

Tot i que feu servir l’aplicació estàndard amb la seva configuració per defecte, també és possible passar-li alguns paràmetres per controlar determinats aspectes del vídeo generat, com ara la qualitat, la seva durada màxima i la mida màxima del vídeo gravat. Aquestes opcions s’han d’especificar un cop creat l’intent, però abans d’executar-lo:

  1. public void onClickBotoGravar(View view) {
  2. // Es crea l'intent i es llança
  3. Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
  4. // Qualitat del vídeo: 1 és alta, 0 baixa
  5. intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
  6. // Durada màxima del vídeo en segons
  7. intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
  8. // Tamany màxim del vídeo en bytes
  9. intent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, 100000);
  10. // Començar la gravació
  11. startActivityForResult(intent, INTENT_GRAVAR_VIDEO);
  12. }
Anar a la pàgina anterior:
Annexos
Anar a la pàgina següent:
Activitats