Apuntes DAM
    Tema 3 de 6PMDM · 2º DAM

    Programación Multimedia y Dispositivos Móviles

    Multimedia en Aplicaciones Móviles

    Formatos, códecs y contenedores; imágenes y gráficos 2D, animaciones, audio con ExoPlayer y SoundPool, grabación, vídeo, cámara con CameraX y multimedia en la web, con laboratorios en el navegador.

    60 min lecturaIntermedio

    1. Contenido multimedia: formatos, códecs y contenedores

    Una aplicación multimedia integra imágenes, audio, vídeo y animaciones. Antes de programar hay que entender cómo se almacenan: los datos sin comprimir ocupan muchísimo (un minuto de vídeo Full HD sin comprimir son unos 10 GB), así que siempre se codifican con un códec y se guardan en un contenedor.

    Códec y contenedor 🎬

    🗜️
    Códec
    El método de compresión (H.264, AAC): cómo se «encoge» el vídeo o el audio y cómo se recupera.
    📦
    Contenedor
    La caja (MP4, MKV, WebM) que guarda juntas las pistas de vídeo, audio y subtítulos.
    ⚖️
    Con o sin pérdida
    Con pérdida (JPEG, MP3) descarta detalles que apenas se notan; sin pérdida (PNG, FLAC) no pierde nada.
    TipoFormato / códecCompresiónUso
    ImagenJPEGCon pérdidaFotografías
    ImagenPNGSin pérdida, transparenciaIconos, capturas, gráficos
    ImagenWebP / AVIFAmbas, más eficienteWeb y apps modernas
    ImagenSVG / Vector DrawableVectorialIconos escalables (en Android, VectorDrawable)
    AudioMP3, AACCon pérdidaMúsica, podcasts
    AudioOpus, Vorbis (OGG)Con pérdida, libreVoz, streaming, juegos
    AudioWAV, FLACSin compresión / sin pérdidaEdición, efectos cortos
    VídeoH.264 (AVC)Con pérdidaEl más compatible
    VídeoH.265 (HEVC), VP9, AV1Con pérdida, más eficiente4K, streaming (AV1 es libre)
    ContenedorMP4, WebM, MKV, 3GP—Agrupan pistas de audio, vídeo y subtítulos
    • Resolución (píxeles), profundidad de color (bits por píxel), fotogramas por segundo, tasa de bits (kbps: calidad frente a tamaño) y, en audio, frecuencia de muestreo (44,1 kHz) y canales.
    • Streaming adaptativo (HLS, MPEG-DASH): el vídeo se divide en trozos en varias calidades y el reproductor elige la que permite la conexión en cada momento.
    • Al integrar contenido de terceros hay que respetar sus licencias (Creative Commons, royalty free) y los derechos de autor.
    Te EXPLICO qué CODECS y FORMATOS de VÍDEO elegir!!!! — Pablo Lozano - Palocha

    2. Imágenes y gráficos

    Las imágenes consumen mucha memoria: una foto de 12 megapíxeles ocupa ~48 MB en RAM (4 bytes por píxel) aunque el JPEG pese 3 MB. Por eso se cargan reducidas al tamaño en que se muestran, de forma asíncrona y con caché. En Android se usan librerías como Coil (Compose) o Glide.

    Imagenes.kt
    1// Imagen de los recursos (res/drawable) y de Internet con Coil
    2@Composable
    3fun FichaProducto(url: String) {
    4    Column {
    5        Image(painterResource(R.drawable.logo), contentDescription = "Logo de la tienda")
    6        AsyncImage(
    7            model = ImageRequest.Builder(LocalContext.current)
    8                .data(url)
    9                .crossfade(true)
    10                .build(),
    11            placeholder = painterResource(R.drawable.cargando),
    12            error = painterResource(R.drawable.sin_imagen),
    13            contentDescription = "Foto del producto",
    14            contentScale = ContentScale.Crop,
    15            modifier = Modifier.size(160.dp).clip(RoundedCornerShape(12.dp))
    16        )
    17    }
    18}
    19
    20// Dibujar a mano en un lienzo (gráficos 2D)
    21@Composable
    22fun Grafico(valores: List<Float>) {
    23    Canvas(Modifier.fillMaxWidth().height(160.dp)) {
    24        val ancho = size.width / valores.size
    25        valores.forEachIndexed { i, v ->
    26            drawRect(Color(0xFF0E7490), topLeft = Offset(i * ancho + 4, size.height * (1 - v)),
    27                     size = Size(ancho - 8, size.height * v))
    28        }
    29    }
    30}

    Animaciones

    Animaciones.kt
    1var expandido by remember { mutableStateOf(false) }
    2val altura by animateDpAsState(if (expandido) 240.dp else 80.dp, label = "altura")
    3val rotacion by animateFloatAsState(if (expandido) 180f else 0f, label = "rotacion")
    4
    5Card(Modifier.height(altura).clickable { expandido = !expandido }) {
    6    Icon(Icons.Default.ExpandMore, null, Modifier.rotate(rotacion))
    7    AnimatedVisibility(expandido) { Text("Detalles del producto…") }
    8}
    9// Animaciones complejas diseñadas por diseñadores: Lottie (ficheros JSON exportados de After Effects)
    Gráficos 2D en el navegador con <canvas>
    Consola
    (sin mensajes)
    Tutorial Canvas: Dibuja en tu navegador usando HTML, CSS y JavaScript — Programación en español

    3. Reproducir y grabar audio

    Clase (Android)Para qué
    ExoPlayer (Jetpack Media3): la opción recomendada para música, podcasts y streaming (HLS, DASH), con listas de reproducción
    MediaPlayerReproductor clásico del sistema, sencillo para un único audio
    SoundPoolEfectos cortos cargados en memoria con muy poca latencia (juegos, pulsaciones)
    MediaRecorderGrabar audio (y vídeo) del micrófono a un fichero
    AudioManagerVolumen y foco de audio: pausar la música cuando entra una llamada
    MediaSessionServiceReproducir en segundo plano con controles en la pantalla de bloqueo y en los auriculares
    Reproductor.kt
    1// build.gradle: implementation("androidx.media3:media3-exoplayer:1.4.1") y media3-ui
    2@Composable
    3fun ReproductorAudio(url: String) {
    4    val contexto = LocalContext.current
    5    val jugador = remember {
    6        ExoPlayer.Builder(contexto).build().apply {
    7            setMediaItem(MediaItem.fromUri(url))
    8            prepare()
    9        }
    10    }
    11    DisposableEffect(Unit) { onDispose { jugador.release() } }   // liberar siempre los recursos
    12
    13    var reproduciendo by remember { mutableStateOf(false) }
    14    IconButton(onClick = {
    15        if (jugador.isPlaying) jugador.pause() else jugador.play()
    16        reproduciendo = jugador.isPlaying
    17    }) {
    18        Icon(if (reproduciendo) Icons.Default.Pause else Icons.Default.PlayArrow, "Reproducir o pausar")
    19    }
    20}
    21
    22// Efectos de sonido con SoundPool
    23val sonidos = SoundPool.Builder().setMaxStreams(4).build()
    24val idSalto = sonidos.load(contexto, R.raw.salto, 1)
    25sonidos.play(idSalto, 1f, 1f, 1, 0, 1f)          // volumen izq, dcho, prioridad, bucles, velocidad
    26
    27// Grabar (requiere el permiso RECORD_AUDIO en tiempo de ejecución)
    28val grabadora = MediaRecorder(contexto).apply {
    29    setAudioSource(MediaRecorder.AudioSource.MIC)
    30    setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
    31    setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
    32    setOutputFile(File(contexto.filesDir, "nota.m4a"))
    33    prepare(); start()
    34}
    35// … grabadora.stop(); grabadora.release()
    Síntesis de audio con la Web Audio API (pulsa las teclas)
    Consola
    (sin mensajes)
    How to Build a Simple Video Player With Jetpack Compose & ExoPlayer (Media3) — Philipp Lackner

    4. Vídeo y cámara

    Para reproducir vídeo se usa también ExoPlayer con su vista PlayerView (controles, pantalla completa, subtítulos). Para capturar fotos y vídeo, CameraX simplifica la cámara del dispositivo; para casos sencillos basta con pedir una foto a la app de cámara del sistema.

    Camara.kt
    1// 1) Foto con la app de cámara del sistema (sin permisos de cámara propios)
    2val uri = FileProvider.getUriForFile(contexto, "${contexto.packageName}.fotos", File(contexto.cacheDir, "foto.jpg"))
    3val hacerFoto = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { ok ->
    4    if (ok) imagen = uri
    5}
    6Button(onClick = { hacerFoto.launch(uri) }) { Text("Hacer foto") }
    7
    8// 2) Elegir una imagen o vídeo de la galería (Photo Picker, sin permisos)
    9val elegir = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> imagen = uri }
    10Button(onClick = { elegir.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) }) {
    11    Text("Elegir de la galería")
    12}
    13
    14// 3) Vista previa y captura dentro de la app con CameraX (requiere el permiso CAMERA)
    15val controlador = remember { LifecycleCameraController(contexto) }
    16AndroidView(factory = { PreviewView(it).apply { this.controller = controlador } })
    17controlador.bindToLifecycle(lifecycleOwner)
    18controlador.takePicture(opcionesSalida, ejecutor, callback)

    Permisos y privacidad

    Cámara (CAMERA), micrófono (RECORD_AUDIO) y fotos del usuario son permisos peligrosos: se piden en tiempo de ejecución, justo cuando hacen falta y explicando para qué. Siempre que se pueda se usa el selector de fotos o la app de cámara del sistema, que no necesitan permisos. Libera la cámara y los reproductores cuando la pantalla deja de verse para no gastar batería.

    Multimedia en la web

    HTML5 incluye <video> y <audio> con una API de JavaScript (play(), pause(), currentTime, volume, muted y eventos play, pause, timeupdate, ended), <canvas> para dibujar y la Web Audio API para generar y procesar sonido. Con ellas se construyen reproductores propios, como en el reto siguiente.

    🧩JS · DOMControles propios para un reproductorMedio

    El <video> no muestra los controles del navegador: hazlos tú. • #play alterna entre reproducir y pausar; su texto es «▶ Reproducir» cuando está pausado y «⏸ Pausa» cuando se reproduce (usa los eventos play y pause del vídeo, no el clic, para actualizar el texto). • #silencio alterna video.muted y su texto entre «🔇 Silenciar» y «🔊 Sonido». • El deslizador #volumen (0 a 100) fija video.volume (0 a 1) y escribe «Volumen: N %» en #nivel. • #adelantar avanza 10 segundos (sin pasar de la duración).

    index.htmlsolo lectura
    script.js
    Vista previa
    How to Build a Camera App With CameraX - Taking Photos — Philipp Lackner

    5. Buenas prácticas en aplicaciones multimedia

    1. Adapta los recursos a la densidad de pantalla (carpetas drawable-mdpi…xxhdpi o vectores) y al ancho de banda.
    2. Carga en segundo plano y muestra un indicador; nunca bloquees el hilo principal.
    3. Caché de imágenes y descargas para no gastar datos del usuario.
    4. Libera reproductores, cámara y sensores en onStop/onDispose.
    5. Gestiona el foco de audio: pausa si otra app empieza a sonar o entra una llamada.
    6. Accesibilidad: contentDescription en las imágenes, subtítulos en los vídeos, controles grandes.
    7. Respeta los derechos del contenido y la privacidad de fotos y grabaciones.

    6. Ejercicios

    Ejercicio Práctico
    Fácil

    Elige formatos

    Elige el formato más adecuado y justifícalo: 1. El icono de la app, que debe verse nítido en cualquier pantalla. 2. Las fotos de los productos de una tienda. 3. El efecto de «moneda» de un juego, que suena muchas veces por segundo. 4. La música de fondo de un juego (3 minutos). 5. Los vídeos de un curso que se ven en móviles con conexiones de todo tipo.

    Ejercicio Práctico
    Difícil

    Grabadora de notas de voz

    Diseña una app Android (Compose) para grabar notas de voz: botón para grabar/parar, lista de grabaciones con fecha y duración, reproducir cada una y borrarla. Indica los permisos, las clases que usarías para grabar y reproducir, dónde guardas los ficheros, cómo gestionas el ciclo de vida (qué pasa si el usuario sale de la app mientras graba) y cómo muestras la duración.

    ¿Has terminado este tema?

    Crea una cuenta gratis para guardar qué temas has terminado, subir de nivel y ganar medallas.

    Guardar mi progreso