{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser","title":"WebCodecs API: Codificación y Decodificación de Video Acelerada por Hardware en el Navegador","description":"Encoding y decoding de video de bajo nivel sin [FFmpeg.wasm](https://ffmpegwasm.netlify.app/), VideoEncoder, VideoDecoder, encoding AV1, y construyendo un pipeline de efectos de video en tiempo real enteramente en el navegador.","language":"es","datePublished":"2026-03-15","dateModified":null,"contentFormat":"text/markdown","content":"Web APIs\n\n# WebCodecs API: Codificación y Decodificación de Video Acelerada por Hardware en el Navegador\n\n2026-03-156 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/webcodecs-api-video-processing-browser>)\n\n## Descripción General de WebCodecs\n\nWebCodecs se sienta debajo de las APIs de media de alto nivel (` <video> `, MediaRecorder, MediaStream) y arriba del nivel de manipulación de bytes crudos. Te da:\n\n- **VideoDecoder**, Decodear frames de video comprimidos (H.264, VP8, VP9, AV1) en objetos ` VideoFrame ` crudos\n- **VideoEncoder**, Encodear objetos ` VideoFrame ` crudos en chunks comprimidos\n- **AudioDecoder/AudioEncoder**, Lo mismo para audio\n- **VideoFrame**, Un frame de video crudo que puede venir de un canvas, cámara o decoder\n- **EncodedVideoChunk**, Un frame de video comprimido listo para muxing\n\nLa clave: estos están **acelerados por hardware**. Cuando creás un VideoEncoder para H.264, el navegador delega al ASIC de encoding dedicado de la GPU (NVENC en NVIDIA, VCE en AMD, Quick Sync en Intel).\n\n## Pipeline de Encoding Básico\n\nEmpecemos con los fundamentos, encodear una animación de canse va a video H.264:\n\n```javascript\n// Paso 1: Verificar soporte del codec\nconst support = await VideoEncoder.isConfigSupported({\n codec: 'avc1.42001f', // H.264 Baseline Level 3.1\n width: 1920,\n height: 1080,\n bitrate: 5_000_000, // 5 Mbps\n framerate: 30,\n});\n\nif (!support.supported) {\n console.error('Encoding H.264 no soportado');\n}\n\n// Paso 2: Recolectar chunks encodeados\nconst chunks = [];\nconst encoder = new VideoEncoder({\n output: (chunk, metadata) => {\n const buffer = new ArrayBuffer(chunk.byteLength);\n chunk.copyTo(buffer);\n\n chunks.push({\n type: chunk.type, // 'key' o 'delta'\n timestamp: chunk.timestamp,\n duration: chunk.duration,\n data: buffer,\n decoderConfig: metadata?.decoderConfig || null,\n });\n },\n error: (e) => console.error('Error de encoder:', e),\n});\n\n// Paso 3: Configurar el encoder\nencoder.configure({\n codec: 'avc1.42001f',\n width: 1920,\n height: 1080,\n bitrate: 5_000_000,\n framerate: 30,\n latencyMode: 'quality',\n avc: { format: 'annexb' },\n});\n\n// Paso 4: Encodear frames desde un canvas\nconst canvas = document.getElementById('mi-canvas');\nconst ctx = canvas.getContext('2d');\n\nfor (let i = 0; i < 300; i++) { // 10 segundos a 30fps\n renderFrame(ctx, i);\n\n const frame = new VideoFrame(canvas, {\n timestamp: i * (1_000_000 / 30), // ¡Microsegundos!\n duration: 1_000_000 / 30,\n });\n\n const keyFrame = i % 90 === 0; // Keyframe cada 3 segundos\n encoder.encode(frame, { keyFrame });\n\n // CRÍTICO: cerrar el frame para liberar memoria GPU\n frame.close();\n}\n\n// Paso 5: Flush frames restantes\nawait encoder.flush();\nencoder.close();\n```\n\nEse ` frame.close() ` es esencial. Los VideoFrames mantienen referencias a texturas GPU. Si te olvida de cerrarlos, se filtra memoria GPU y eventualmente el navegador termina la pestaña. Sin manejo apropiado del ciclo de vida de frames, las aplicaciones de procesamiento de video comúnmente fallan después de ~200 frames.\n\n## Pipeline de Efectos de Video en Tiempo Real\n\nA continuación, es donde WebCodecs resulta particularmente relevante, armar un pipeline de efectos en tiempo real que toma input de cámara, aplica efectos acelerados por GPU vía WebGL, y encodea el resultado:\n\n```javascript\nclass VideoEffectsPipeline {\n constructor(outputCanvas) {\n this.outputCanvas = outputCanvas;\n this.gl = outputCanvas.getContext('webgl2');\n this.encoder = null;\n this.running = false;\n this.setupShaders();\n }\n\n setupShaders() {\n const fragmentShader = `#version 300 es\n precision highp float;\n in vec2 v_texCoord;\n out vec4 outColor;\n uniform sampler2D u_texture;\n uniform float u_time;\n\n void main() {\n vec4 color = texture(u_texture, v_texCoord);\n\n // Tono sepia\n float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n vec3 sepia = vec3(gray) * vec3(1.2, 1.0, 0.8);\n\n // Viñeta\n vec2 center = v_texCoord - 0.5;\n float dist = length(center);\n float vignette = smoothstep(0.7, 0.4, dist);\n\n // Aberración cromática animada\n float offset = sin(u_time * 2.0) * 0.002;\n float r = texture(u_texture, v_texCoord + vec2(offset, 0.0)).r;\n float b = texture(u_texture, v_texCoord - vec2(offset, 0.0)).b;\n\n outColor = vec4(\n mix(r, sepia.r, 0.5) * vignette,\n mix(color.g, sepia.g, 0.5) * vignette,\n mix(b, sepia.b, 0.5) * vignette,\n 1.0\n );\n }\n `;\n this.program = this.compileShaderProgram(fragmentShader);\n this.timeUniform = this.gl.getUniformLocation(this.program, 'u_time');\n }\n\n async start(mediaStream) {\n const videoTrack = mediaStream.getVideoTracks()[0];\n const { width, height } = videoTrack.getSettings();\n\n const processor = new MediaStreamTrackProcessor({ track: videoTrack });\n const reader = processor.readable.getReader();\n\n this.encoder = new VideoEncoder({\n output: (chunk, meta) => this.handleEncodedChunk(chunk, meta),\n error: (e) => console.error('Error encoding:', e),\n });\n\n this.encoder.configure({\n codec: 'avc1.42001f',\n width,\n height,\n bitrate: 3_000_000,\n framerate: 30,\n latencyMode: 'realtime',\n });\n\n this.running = true;\n let frameCount = 0;\n\n while (this.running) {\n const { value: frame, done } = await reader.read();\n if (done) break;\n\n const processedFrame = this.applyEffect(frame, frameCount);\n frame.close();\n\n const keyFrame = frameCount % 90 === 0;\n this.encoder.encode(processedFrame, { keyFrame });\n processedFrame.close();\n\n frameCount++;\n }\n\n await this.encoder.flush();\n this.encoder.close();\n }\n\n applyEffect(inputFrame, frameIndex) {\n const gl = this.gl;\n\n gl.useProgram(this.program);\n gl.uniform1f(this.timeUniform, frameIndex / 30.0);\n\n // Subir VideoFrame como textura, zero-copy en navegadores soportados\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(\n gl.TEXTURE_2D, 0, gl.RGBA,\n gl.RGBA, gl.UNSIGNED_BYTE,\n inputFrame // ¡VideoFrame directo como fuente de textura!\n );\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n const outputFrame = new VideoFrame(this.outputCanvas, {\n timestamp: inputFrame.timestamp,\n duration: inputFrame.duration,\n });\n\n gl.deleteTexture(texture);\n return outputFrame;\n }\n}\n```\n\nLa línea mágica es ` gl.texImage2D(..., inputFrame) `. En Chrome, cuando el buffer subyacente del VideoFrame es una textura GPU (que es así cuando viene de una cámara o decoder), esto es una operación zero-copy.\n\n## Encoding AV1: La Próxima Generación\n\n```javascript\nasync function getOptimalCodec(width, height) {\n const av1Config = {\n codec: 'av01.0.08M.08',\n width, height,\n bitrate: 3_000_000,\n framerate: 30,\n };\n\n const av1Support = await VideoEncoder.isConfigSupported(av1Config);\n if (av1Support.supported) return { ...av1Config, label: 'AV1 (HW)' };\n\n const vp9Config = {\n codec: 'vp09.00.31.08',\n width, height,\n bitrate: 4_000_000,\n framerate: 30,\n };\n\n const vp9Support = await VideoEncoder.isConfigSupported(vp9Config);\n if (vp9Support.supported) return { ...vp9Config, label: 'VP9' };\n\n return {\n codec: 'avc1.42001f',\n width, height,\n bitrate: 5_000_000,\n framerate: 30,\n avc: { format: 'annexb' },\n label: 'H.264',\n };\n}\n```\n\n## Comparación de Performance\n\nEncoding de video 1080p30 por 60 segundos en un MacBook Pro 2024 (M3 Pro):\n\n| Método | Tiempo Encoding | Pico Memoria | Uso CPU |\n| --- | --- | --- | --- |\n| FFmpeg.wasm (H.264) | 4 min 12s | 890MB | 100% (1 core) |\n| WebCodecs (H.264 HW) | 6.2s | 125MB | 15% |\n| WebCodecs (VP9 HW) | 8.1s | 130MB | 18% |\n| WebCodecs (AV1 HW) | 11.4s | 145MB | 22% |\n| WebCodecs (AV1 SW fallback) | 3 min 38s | 310MB | 95% (1 core) |\n\nLos números de aceleración por hardware son impresionantes. 6.2 segundos vs 4 minutos para encoding H.264 del mismo contenido.\n\n## El Error Más Común\n\n**Los timestamps de VideoFrame son en microsegundos, no milisegundos.** Este bug aparece frecuentemente en distintas codebases. Multiplicás por 1000 en vez de 1,000,000 y tu video encodeado se reproduce a 1000x de velocidad.\n\n```javascript\n// MAL, timestamps en milisegundos\nconst frame = new VideoFrame(canvas, {\n timestamp: Date.now(), // ← milisegundos, interpretado como microsegundos\n});\n\n// BIEN, timestamps en microsegundos\nconst frame = new VideoFrame(canvas, {\n timestamp: performance.now() * 1000, // ← convertir ms a μs\n});\n```\n\nWebCodecs representa un avance significativo para la plataforma web. Por primera vez, podemos hacer procesamiento de video real en el navegador sin cargar un binario WASM de muchos megas, sin saturar la CPU, y con calidad que coincide con aplicaciones nativas. Las herramientas de edición de video, streaming y comunicación que se están construyendo sobre esta API van a ser notables.\n\n## Preguntas frecuentes\n\n### ¿Qué es la API WebCodecs?\n\nWebCodecs es una API del navegador que provee acceso de bajo nivel a encoders y decoders de video y audio acelerados por hardware. A diferencia de MediaRecorder o elementos video, WebCodecs te da control frame a frame sobre parámetros de encoding, selección de codec y formato de salida, sin cargar un FFmpeg basado en WASM.\n\n### ¿Puedo encodear video AV1 en el navegador con WebCodecs?\n\nSí, si el hardware del usuario lo soporta. WebCodecs puede usar encoders AV1 por hardware en máquinas con GPUs Intel Gen12+, AMD RDNA3+, o NVIDIA Ada Lovelace+.\n\n### ¿Cómo se compara WebCodecs con FFmpeg.wasm?\n\nWebCodecs usa las implementaciones nativas de codecs del navegador (frecuentemente aceleradas por hardware), mientras FFmpeg.wasm corre un binario FFmpeg completo compilado a WebAssembly. WebCodecs es más rápido, usa menos memoria y no requiere descargar un binario WASM de 25MB+.\n\n## Tips\n\n1. **Tip:** Siempre verificar ` VideoEncoder.isConfigSupported() ` antes de crear tu encoder, el soporte de encoding AV1 por hardware varía mucho. Chrome en una máquina con GPU Intel Arc lo soporta, el mismo Chrome en una NVIDIA vieja no. Fallback graceful a VP9 o H.264.\n2. **Tip:** Mover tu VideoEncoder a un Web Worker dedicado. Encodear incluso un solo frame 1080p bloquea el main thread por 2-8ms dependiendo del codec. A 30fps eso son 60-240ms por segundo de encoding jank que mata la responsividad de tu UI.\n3. **Tip:** Establecer ` latencyMode: 'realtime' ` en tu config de VideoEncoder para case es de uso de streaming en vivo. El modo ` 'quality' ` por defecto bufferea frames para mejor compresión, agregando 100-300ms de latencia inaceptable para videollamadas.\n\n- webcodecs\n- procesamiento-video\n- av1\n- browser-api\n- video-encoder\n- aceleración-hardware\n- webworkers\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"WebCodecs API: Codificación y Decodificación de Video Acelerada por Hardware en el Navegador","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#title"},{"level":2,"text":"Descripción General de WebCodecs","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#descripcin-general-de-webcodecs"},{"level":2,"text":"Pipeline de Encoding Básico","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#pipeline-de-encoding-bsico"},{"level":2,"text":"Pipeline de Efectos de Video en Tiempo Real","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#pipeline-de-efectos-de-video-en-tiempo-real"},{"level":2,"text":"Encoding AV1: La Próxima Generación","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#encoding-av1-la-prxima-generacin"},{"level":2,"text":"Comparación de Performance","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#comparacin-de-performance"},{"level":2,"text":"El Error Más Común","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#el-error-ms-comn"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#questions"},{"level":3,"text":"¿Qué es la API WebCodecs?","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#question-1"},{"level":3,"text":"¿Puedo encodear video AV1 en el navegador con WebCodecs?","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#question-2"},{"level":3,"text":"¿Cómo se compara WebCodecs con FFmpeg.wasm?","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#article-tips"}],"sections":[{"heading":"WebCodecs API: Codificación y Decodificación de Video Acelerada por Hardware en el Navegador","level":1,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser","content":"Web APIs"},{"heading":"WebCodecs API: Codificación y Decodificación de Video Acelerada por Hardware en el Navegador","level":1,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#title","content":"2026-03-156 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/webcodecs-api-video-processing-browser>)"},{"heading":"Descripción General de WebCodecs","level":2,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#descripcin-general-de-webcodecs","content":"WebCodecs se sienta debajo de las APIs de media de alto nivel (` <video> `, MediaRecorder, MediaStream) y arriba del nivel de manipulación de bytes crudos. Te da:\n\n- **VideoDecoder**, Decodear frames de video comprimidos (H.264, VP8, VP9, AV1) en objetos ` VideoFrame ` crudos\n- **VideoEncoder**, Encodear objetos ` VideoFrame ` crudos en chunks comprimidos\n- **AudioDecoder/AudioEncoder**, Lo mismo para audio\n- **VideoFrame**, Un frame de video crudo que puede venir de un canvas, cámara o decoder\n- **EncodedVideoChunk**, Un frame de video comprimido listo para muxing\n\nLa clave: estos están **acelerados por hardware**. Cuando creás un VideoEncoder para H.264, el navegador delega al ASIC de encoding dedicado de la GPU (NVENC en NVIDIA, VCE en AMD, Quick Sync en Intel)."},{"heading":"Pipeline de Encoding Básico","level":2,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#pipeline-de-encoding-bsico","content":"Empecemos con los fundamentos, encodear una animación de canse va a video H.264:\n\n```javascript\n// Paso 1: Verificar soporte del codec\nconst support = await VideoEncoder.isConfigSupported({\n codec: 'avc1.42001f', // H.264 Baseline Level 3.1\n width: 1920,\n height: 1080,\n bitrate: 5_000_000, // 5 Mbps\n framerate: 30,\n});\n\nif (!support.supported) {\n console.error('Encoding H.264 no soportado');\n}\n\n// Paso 2: Recolectar chunks encodeados\nconst chunks = [];\nconst encoder = new VideoEncoder({\n output: (chunk, metadata) => {\n const buffer = new ArrayBuffer(chunk.byteLength);\n chunk.copyTo(buffer);\n\n chunks.push({\n type: chunk.type, // 'key' o 'delta'\n timestamp: chunk.timestamp,\n duration: chunk.duration,\n data: buffer,\n decoderConfig: metadata?.decoderConfig || null,\n });\n },\n error: (e) => console.error('Error de encoder:', e),\n});\n\n// Paso 3: Configurar el encoder\nencoder.configure({\n codec: 'avc1.42001f',\n width: 1920,\n height: 1080,\n bitrate: 5_000_000,\n framerate: 30,\n latencyMode: 'quality',\n avc: { format: 'annexb' },\n});\n\n// Paso 4: Encodear frames desde un canvas\nconst canvas = document.getElementById('mi-canvas');\nconst ctx = canvas.getContext('2d');\n\nfor (let i = 0; i < 300; i++) { // 10 segundos a 30fps\n renderFrame(ctx, i);\n\n const frame = new VideoFrame(canvas, {\n timestamp: i * (1_000_000 / 30), // ¡Microsegundos!\n duration: 1_000_000 / 30,\n });\n\n const keyFrame = i % 90 === 0; // Keyframe cada 3 segundos\n encoder.encode(frame, { keyFrame });\n\n // CRÍTICO: cerrar el frame para liberar memoria GPU\n frame.close();\n}\n\n// Paso 5: Flush frames restantes\nawait encoder.flush();\nencoder.close();\n```\n\nEse ` frame.close() ` es esencial. Los VideoFrames mantienen referencias a texturas GPU. Si te olvida de cerrarlos, se filtra memoria GPU y eventualmente el navegador termina la pestaña. Sin manejo apropiado del ciclo de vida de frames, las aplicaciones de procesamiento de video comúnmente fallan después de ~200 frames."},{"heading":"Pipeline de Efectos de Video en Tiempo Real","level":2,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#pipeline-de-efectos-de-video-en-tiempo-real","content":"A continuación, es donde WebCodecs resulta particularmente relevante, armar un pipeline de efectos en tiempo real que toma input de cámara, aplica efectos acelerados por GPU vía WebGL, y encodea el resultado:\n\n```javascript\nclass VideoEffectsPipeline {\n constructor(outputCanvas) {\n this.outputCanvas = outputCanvas;\n this.gl = outputCanvas.getContext('webgl2');\n this.encoder = null;\n this.running = false;\n this.setupShaders();\n }\n\n setupShaders() {\n const fragmentShader = `#version 300 es\n precision highp float;\n in vec2 v_texCoord;\n out vec4 outColor;\n uniform sampler2D u_texture;\n uniform float u_time;\n\n void main() {\n vec4 color = texture(u_texture, v_texCoord);\n\n // Tono sepia\n float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n vec3 sepia = vec3(gray) * vec3(1.2, 1.0, 0.8);\n\n // Viñeta\n vec2 center = v_texCoord - 0.5;\n float dist = length(center);\n float vignette = smoothstep(0.7, 0.4, dist);\n\n // Aberración cromática animada\n float offset = sin(u_time * 2.0) * 0.002;\n float r = texture(u_texture, v_texCoord + vec2(offset, 0.0)).r;\n float b = texture(u_texture, v_texCoord - vec2(offset, 0.0)).b;\n\n outColor = vec4(\n mix(r, sepia.r, 0.5) * vignette,\n mix(color.g, sepia.g, 0.5) * vignette,\n mix(b, sepia.b, 0.5) * vignette,\n 1.0\n );\n }\n `;\n this.program = this.compileShaderProgram(fragmentShader);\n this.timeUniform = this.gl.getUniformLocation(this.program, 'u_time');\n }\n\n async start(mediaStream) {\n const videoTrack = mediaStream.getVideoTracks()[0];\n const { width, height } = videoTrack.getSettings();\n\n const processor = new MediaStreamTrackProcessor({ track: videoTrack });\n const reader = processor.readable.getReader();\n\n this.encoder = new VideoEncoder({\n output: (chunk, meta) => this.handleEncodedChunk(chunk, meta),\n error: (e) => console.error('Error encoding:', e),\n });\n\n this.encoder.configure({\n codec: 'avc1.42001f',\n width,\n height,\n bitrate: 3_000_000,\n framerate: 30,\n latencyMode: 'realtime',\n });\n\n this.running = true;\n let frameCount = 0;\n\n while (this.running) {\n const { value: frame, done } = await reader.read();\n if (done) break;\n\n const processedFrame = this.applyEffect(frame, frameCount);\n frame.close();\n\n const keyFrame = frameCount % 90 === 0;\n this.encoder.encode(processedFrame, { keyFrame });\n processedFrame.close();\n\n frameCount++;\n }\n\n await this.encoder.flush();\n this.encoder.close();\n }\n\n applyEffect(inputFrame, frameIndex) {\n const gl = this.gl;\n\n gl.useProgram(this.program);\n gl.uniform1f(this.timeUniform, frameIndex / 30.0);\n\n // Subir VideoFrame como textura, zero-copy en navegadores soportados\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(\n gl.TEXTURE_2D, 0, gl.RGBA,\n gl.RGBA, gl.UNSIGNED_BYTE,\n inputFrame // ¡VideoFrame directo como fuente de textura!\n );\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n const outputFrame = new VideoFrame(this.outputCanvas, {\n timestamp: inputFrame.timestamp,\n duration: inputFrame.duration,\n });\n\n gl.deleteTexture(texture);\n return outputFrame;\n }\n}\n```\n\nLa línea mágica es ` gl.texImage2D(..., inputFrame) `. En Chrome, cuando el buffer subyacente del VideoFrame es una textura GPU (que es así cuando viene de una cámara o decoder), esto es una operación zero-copy."},{"heading":"Encoding AV1: La Próxima Generación","level":2,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#encoding-av1-la-prxima-generacin","content":"```javascript\nasync function getOptimalCodec(width, height) {\n const av1Config = {\n codec: 'av01.0.08M.08',\n width, height,\n bitrate: 3_000_000,\n framerate: 30,\n };\n\n const av1Support = await VideoEncoder.isConfigSupported(av1Config);\n if (av1Support.supported) return { ...av1Config, label: 'AV1 (HW)' };\n\n const vp9Config = {\n codec: 'vp09.00.31.08',\n width, height,\n bitrate: 4_000_000,\n framerate: 30,\n };\n\n const vp9Support = await VideoEncoder.isConfigSupported(vp9Config);\n if (vp9Support.supported) return { ...vp9Config, label: 'VP9' };\n\n return {\n codec: 'avc1.42001f',\n width, height,\n bitrate: 5_000_000,\n framerate: 30,\n avc: { format: 'annexb' },\n label: 'H.264',\n };\n}\n```"},{"heading":"Comparación de Performance","level":2,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#comparacin-de-performance","content":"Encoding de video 1080p30 por 60 segundos en un MacBook Pro 2024 (M3 Pro):\n\n| Método | Tiempo Encoding | Pico Memoria | Uso CPU |\n| --- | --- | --- | --- |\n| FFmpeg.wasm (H.264) | 4 min 12s | 890MB | 100% (1 core) |\n| WebCodecs (H.264 HW) | 6.2s | 125MB | 15% |\n| WebCodecs (VP9 HW) | 8.1s | 130MB | 18% |\n| WebCodecs (AV1 HW) | 11.4s | 145MB | 22% |\n| WebCodecs (AV1 SW fallback) | 3 min 38s | 310MB | 95% (1 core) |\n\nLos números de aceleración por hardware son impresionantes. 6.2 segundos vs 4 minutos para encoding H.264 del mismo contenido."},{"heading":"El Error Más Común","level":2,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#el-error-ms-comn","content":"**Los timestamps de VideoFrame son en microsegundos, no milisegundos.** Este bug aparece frecuentemente en distintas codebases. Multiplicás por 1000 en vez de 1,000,000 y tu video encodeado se reproduce a 1000x de velocidad.\n\n```javascript\n// MAL, timestamps en milisegundos\nconst frame = new VideoFrame(canvas, {\n timestamp: Date.now(), // ← milisegundos, interpretado como microsegundos\n});\n\n// BIEN, timestamps en microsegundos\nconst frame = new VideoFrame(canvas, {\n timestamp: performance.now() * 1000, // ← convertir ms a μs\n});\n```\n\nWebCodecs representa un avance significativo para la plataforma web. Por primera vez, podemos hacer procesamiento de video real en el navegador sin cargar un binario WASM de muchos megas, sin saturar la CPU, y con calidad que coincide con aplicaciones nativas. Las herramientas de edición de video, streaming y comunicación que se están construyendo sobre esta API van a ser notables."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#questions","content":""},{"heading":"¿Qué es la API WebCodecs?","level":3,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#question-1","content":"WebCodecs es una API del navegador que provee acceso de bajo nivel a encoders y decoders de video y audio acelerados por hardware. A diferencia de MediaRecorder o elementos video, WebCodecs te da control frame a frame sobre parámetros de encoding, selección de codec y formato de salida, sin cargar un FFmpeg basado en WASM."},{"heading":"¿Puedo encodear video AV1 en el navegador con WebCodecs?","level":3,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#question-2","content":"Sí, si el hardware del usuario lo soporta. WebCodecs puede usar encoders AV1 por hardware en máquinas con GPUs Intel Gen12+, AMD RDNA3+, o NVIDIA Ada Lovelace+."},{"heading":"¿Cómo se compara WebCodecs con FFmpeg.wasm?","level":3,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#question-3","content":"WebCodecs usa las implementaciones nativas de codecs del navegador (frecuentemente aceleradas por hardware), mientras FFmpeg.wasm corre un binario FFmpeg completo compilado a WebAssembly. WebCodecs es más rápido, usa menos memoria y no requiere descargar un binario WASM de 25MB+."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#article-tips","content":"1. **Tip:** Siempre verificar ` VideoEncoder.isConfigSupported() ` antes de crear tu encoder, el soporte de encoding AV1 por hardware varía mucho. Chrome en una máquina con GPU Intel Arc lo soporta, el mismo Chrome en una NVIDIA vieja no. Fallback graceful a VP9 o H.264.\n2. **Tip:** Mover tu VideoEncoder a un Web Worker dedicado. Encodear incluso un solo frame 1080p bloquea el main thread por 2-8ms dependiendo del codec. A 30fps eso son 60-240ms por segundo de encoding jank que mata la responsividad de tu UI.\n3. **Tip:** Establecer ` latencyMode: 'realtime' ` en tu config de VideoEncoder para case es de uso de streaming en vivo. El modo ` 'quality' ` por defecto bufferea frames para mejor compresión, agregando 100-300ms de latencia inaceptable para videollamadas.\n\n- webcodecs\n- procesamiento-video\n- av1\n- browser-api\n- video-encoder\n- aceleración-hardware\n- webworkers\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/webcodecs-api-video-processing-browser"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser"},"headline":"WebCodecs API: Codificación y Decodificación de Video Acelerada por Hardware en el Navegador","description":"Encoding y decoding de video de bajo nivel sin [FFmpeg.wasm](https://ffmpegwasm.netlify.app/), VideoEncoder, VideoDecoder, encoding AV1, y construyendo un pipeline de efectos de video en tiempo real enteramente en el navegador.","datePublished":"2026-03-15","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://lucioduran.com/favicons/og-default.png","url":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Web APIs","keywords":"webcodecs, procesamiento-video, av1, browser-api, video-encoder, aceleración-hardware, webworkers","wordCount":1070,"timeRequired":"PT6M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/webcodecs-api-video-processing-browser"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"WebCodecs API: Codificación y Decodificación de Video Acelerada por Hardware en el Navegador","item":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Qué es la API WebCodecs?","acceptedAnswer":{"@type":"Answer","text":"WebCodecs es una API del navegador que provee acceso de bajo nivel a encoders y decoders de video y audio acelerados por hardware. A diferencia de MediaRecorder o elementos video, WebCodecs te da control frame a frame sobre parámetros de encoding, selección de codec y formato de salida, sin cargar un FFmpeg basado en WASM."}},{"@type":"Question","name":"¿Puedo encodear video AV1 en el navegador con WebCodecs?","acceptedAnswer":{"@type":"Answer","text":"Sí, si el hardware del usuario lo soporta. WebCodecs puede usar encoders AV1 por hardware en máquinas con GPUs Intel Gen12+, AMD RDNA3+, o NVIDIA Ada Lovelace+."}},{"@type":"Question","name":"¿Cómo se compara WebCodecs con FFmpeg.wasm?","acceptedAnswer":{"@type":"Answer","text":"WebCodecs usa las implementaciones nativas de codecs del navegador (frecuentemente aceleradas por hardware), mientras FFmpeg.wasm corre un binario FFmpeg completo compilado a WebAssembly. WebCodecs es más rápido, usa menos memoria y no requiere descargar un binario WASM de 25MB+."}}]}]}],"contentHash":"02398e43d9620e3610d41c156c103e381fa58c0107b21295c6cd7f72aec66e89","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/webcodecs-api-video-processing-browser","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":57506,"contentBytes":10566}}
