Xwork's Blog

The lord is waiting to take your hand.

Cumplimos 8 años.

Hola de nuevo a todos. El día de hoy solo escribo para recordar que un día cómo hoy pero de hace ocho años, nació el Blog de Xwork. Siempre le voy a agradecer a todos los que se toman la molestia de venir a leer aunque sea un poco. Si bien la cantidad de publicaciones ha disminuido de forma drástica, tengan por seguro que va a seguir habiendo, a inicio de año planifique todo y de hecho se iba a ir publicando un post al mes, pero por razones que son difíciles de comprender aun para mi, algo salio mal en mayo y todos los post que se iban a publicar, simplemente desaparecieron. Estoy arreglando todo y en cuanto este listo, se subirán de uno por uno.


 

 Sin más que decir, solo quiero agradecer una vez mas a todos los que me leen.

Nos seguimos leyendo.

Att. Xwork.

El Video Correcto #10 - VEncoder y subtitulos.

Hola de nuevo a todos, el día de hoy vamos a ver cómo insertar subtitulos en los videos que se codifiquen con VEncoder.


Hay que recordar que VEncoder es una interfaz de usuario para FFMPEG; si bien la idea es mantener los mas simple posible todo, para lograr resultados complejos, es necesario hacer ajustes complejos. Antes de continuar, me gustaría recapitular cual fue la idea de VEncoder. El programa inicialmente nació cómo una herramienta para convertir los videos para que el Play Station Portable pudiera reproducirlos, incluso era parte de un programa llamado PSP Manager, y cómo su nombre lo pudiera sugerir, servía para manejar el contenido multimedia de la consola. Poco a poco cada una de las partes se separó y dieron origen a VEncoder y AEncoder que a diferencia de PSP Manager, no tuvieron tanta relevancia.


He usado mi PSP desde el 2010 y de las pocas cosas que no he logrado entender, es cómo funciona la opción de los subtítulos en el reproductor de video.


Recientemente me he encontrado con gente para la cual la consola es algo totalmente nuevo y he compartido algunos videos con esas personas, pero cómo los tiempos cambian, ahora resulta más sencillo encontrar contenido que no está en nuestro idioma por lo que resulta necesario encontrar una forma de incluirlos. Cómo mencioné hace poco, a pesar de que la PSP tiene (o eso según el menú) la capacidad de reproducir subtítulos, nunca supe cómo usarlos. 

FFMPEG tiene la capacidad de reproducir subtítulos, pero antes de detallar cómo es que funcionan, hay que resaltar algo importante. Hay dos formas de incrustar los subtítulos; la primera es sencilla, se dota de un archivo que tiene la transcripción de los diálogos con los tiempos para cada uno, el reproductor los lee y los muestra sobre el video, a esto le podemos llamar "subtítulos por software". La otra forma es similar, tenemos un archivo con las transcripciones pero solo las usamos al momento de codificar el video y justo en la codificación, vamos a modificar la imagen correspondiente al tiempo del fotograma, de tal forma que el texto quede cómo parte de la misma, con eso bastará para que los subtítulos queden de forma permanente en el video.

Para lograr esto, usaremos FFMPEG junto con VEncoder (el cual actualizare pronto para que la opción "Si hay subtítulos 'quemarlos'" funcione). En VEncoder, hay una ficha llamada "Parámetros adicionales" dentro, hay una ficha llamada "video". FFMPEG soporta varios tipos de subtítulos, archivos SRT o archivos ASS, los primeros son mas sencillos, pues solo incluyen el dialogo y el tiempo en que deben de aparecer; los segundos incluyen "estilos" y efectos (transiciones, diferentes ubicaciones para el texto).

Primero veamos un ejemplo de cómo insertar un archivo SRT.

 -vf "subtitles='I\:\\\VIDEOS\\\Track3.srt'"
En VEncoder, bastará con agregar la línea anterior a los parámetros adicionales del video, para este caso, tenemos que nuestro archivo de subtítulos se llama "Track3.srt" y que su ruta completa es "I:\VIDEOS\Track3.srt", en FFMPEG para escapar las contra-diagonales, necesitamos usar doble "\". Supongamos que tenemos la siguiente ruta "c:\VIDEOS \Dr House\Season 1\Pilot.srt", la ruta para usarla en FFMPEG quedaria: "c\:\\\VIDEOS\\\Dr House\\\Season 1\\\Pilot.srt".

Si usamos archivos ASS, la sintaxis es similar.

-vf "ass='I\:\\\VIDEOS\\\Track3.srt'"

La codificación para los archivos ass tardará mas tiempo, dependiendo de los estilos y los efectos, por lo que en la medida de lo posible recomiendo usar archivos SRT.

No es precisamente el mismo cuadro, pero se ven lo subtítulos "quemados"

Y bien, por ahora es todo, aun quedan varias cosas que tratar para poder visualizar los videos de forma correcta en el PSP, una de las cosas más comunes, es encontrar la relación de aspecto adecuada para cada video, la resolucion y la calidad, pero eso lo veremos en el siguiente post.

Los leo luego.

Learning machine #18 - Programando en Pascal - Estructuras de datos (Record).

Hola de nuevo a todos, el día de hoy vamos a continuar con mas del aprendizaje del lenguaje de programación en pascal.

"En el capítulo anterior de <<Learning machine>>" vimos cómo es que se usan los arreglos de una o varias dimensiones. Hoy vamos a ver cómo es que se usan las estructuras de datos o "Record" (registro en español). Un registro  básicamente es un conjunto de variables bajo un mismo nombre o tipo.

Para crear un registro, hay que crear un tipo usando la palabra "Type" seguido del nombre o identificador que le daremos al registro, hasta aquí todo es igual a cuando creamos los tipos, ahora la diferencia viene en que hay que asignarlo al tipo "Record", es decir tendríamos algo cómo: "Type NombreDelRegistro = Record" una vez creado, dentro de este nuevo tipo, agregaremos "campos"; cada campo sera una variable de cualquiera de los tipos soportados por Pascal. Una vez agregados todos los campos, finalizaremos la declaracion, con la palabra reservada "end;". Para apreciar un poco mejor cómo es todo el proceso, tomemos el siguiente ejemplo:

Type MiCancion = Record
	Cancion,Artista,Album : string;
	Year : Integer;
	BeatPerMinute : single
end;

Con esto habremos creado un nuevo registro llamado "MiCancion"; este contendrá los campos: "Cancion, Artista, Album, Year y BeatPerMinute" del tipo "string, string, string, integer y single" respectivamente. Para poder usar el registro, crearemos variables del tipo que acabamos de crear.

Si queremos acceder a los campos, bastará con escribir el nombre de la variable seguido de "." y despues el nombre del campo. Para visualizar mejor cómo es que se hace, observemos el siguiente código:

        KillerQueen.Cancion := 'Killer Queen.flac';
	KillerQueen.Artista := 'Queen';
	KillerQueen.Album := 'Sheer Heart Attack';
	KillerQueen.Year := 1974;
	KillerQueen.BeatPerMinute := 117.27;

	writeln(KillerQueen.Cancion);
	writeln(KillerQueen.Artista);
	writeln(KillerQueen.Album);
	writeln(KillerQueen.Year);
	writeln(KillerQueen.BeatPerMinute);

Una forma de agilizar el acceso al registro, es hacer uso de la palabra reservada "with", para ello, escribiremos "with" seguido del nombre del registro al cual queremos acceder seguido de de la palabra reservada "do", luego "begin", para poder acceder a los miembros solo bastará con escribir el nombre de cada campo; y cómo cada sección, la finalizaremos con su respectivo "end;". Ahora revisemos el siguiente código que ejemplifica lo anterior.

with Epitaph do
	begin
		Cancion := 'Epitaph.flac';	
		Artista := 'King Crimson';
		Album := 'In the court of the crimson king';
		Year := 1969;
		BeatPerMinute := 100.37;
	end;

Los registros son utiles a la hora de trabajar con estructuras de datos ya que una vez que se crean y se asignan los valores correspondientes, todo el conjunto pasara a ser parte de la misma variable y si por ejemplo asignamos "KillerQueen := Epitaph" tendremos una copia exacta del primero.

Solo por si se ofrece, dejo el código de un programa completo que hace uso de los registros.

program Registros(output);

uses crt, sysutils;

Type MiCancion = Record
	Cancion,Artista,Album : string;
	Year : Integer;
	BeatPerMinute : single
end;

var

KillerQueen, Epitaph : MiCancion;

begin
	KillerQueen.Cancion := 'Killer Queen.flac';
	KillerQueen.Artista := 'Queen';
	KillerQueen.Album := 'Sheer Heart Attack';
	KillerQueen.Year := 1974;
	KillerQueen.BeatPerMinute := 117.27;

	writeln(KillerQueen.Cancion);
	writeln(KillerQueen.Artista);
	writeln(KillerQueen.Album);
	writeln(KillerQueen.Year);
	writeln(KillerQueen.BeatPerMinute);

	with Epitaph do
	begin
		Cancion := 'Epitaph.flac';	
		Artista := 'King Crimson';
		Album := 'In the court of the crimson king';
		Year := 1969;
		BeatPerMinute := 100.37;
	end;
    
	writeln(Epitaph.Cancion);
	writeln(Epitaph.Artista);
	writeln(Epitaph.Album);
	writeln(Epitaph.Year);
	writeln(Epitaph.BeatPerMinute);
	
	readln();

end.

Y bien, por ahora es todo. El código lo puedes copiar para probarlo. Antes de terminar, hay que aclarar algo, si bien en español, se puede leer "Record" cómo registro, no hay que confundirlo con "Register" que es una cosa completamente diferente (que eventualmente veremos).

Los leo luego.

Bienvenido el 2024


Hola de nuevo a todos, el día de hoy solo escribo para desearles a todos un buen año 2024. Cómo ya se va haciendo costumbre este año trataré de publicar al menos una entrada al mes; el año 2023 fue particularmente bueno y contrario a lo que se pensaría, esa fue la razón por la cual la actividad del blog bajo a niveles no vistos antes. Es bueno ver que aun hay gente que espera seguir leyendo los artículos y que los temas son bastante atinados para ellos. Sin más que decir, espero que sigan pasando a leer.

Si, 2024 y sigo usando mi PSP.


Los leo luego.

Vamos a programar #104 - Leyendo metadatos de archivos FLAC (Pt. 3 Ver. C#)

 Hola de nuevo a todos, el día de hoy vamos a continuar con la última parte de FlacTagReader.


En el post anterior vimos cómo es que funcionan algunas funciones (valga la redundancia) y hoy veremos las faltantes.

La siguiente función es: "NumberToBlockType()". Esta función sirve para convertir un número en su equivalente al de la numeración "BlockTypes". Recibe un parámetro del tipo "uint" que es el número que queremos convertir. Regresa un valor del tipo enumeración "BlockTypes" equivalente. Hay que recordar que nos basamos en la de los tipos de bloques de la documentación.

La siguiente función es: "ReadVorbisData()". Cómo su nombre lo podría sugerir, esta función sirve para leer los datos de tipo vorbis ¿Qué es esto? te preguntaras, este bloque en particular es el que contiene los metadatos tales cómo: álbum, artista, año, etc. Siguen la estructura para los comentarios en archivos ogg vorbis.

La estructura de los comentarios es cómo la que sigue:

  • Tamaño de la descripción del proveedor (32 bits).
  • Cadena de texto para describir al proveedor.
  • Tamaño de la lista de comentarios del usuario (32 bits).
  • Tamaño de la lista de comentarios.
    • Tamaño del elemento actual.
    • Cadena de texto en formato UTF-8
  • Marcador de final del bloque (si es verdadero o no hay se marca error o fin del bloque).
Cada elemento de la lista anterior consta de 3 partes:
  • Nombre del campo.
  • = (símbolo de igual o símbolo ASCII 0x3D).
  • Descripción del campo.

Aunque no es mandatorio seguir una estructura particular para cada nombre de campo, si se recomiendan los siguientes:
  • Los nombres de cada campo no deben de contener símbolos que escapen del estándar ASCII de caracteres imprimibles, es decir: de 0x20 a 0x7D (excluyendo el símbolo de igual "=" o 0x3D) y de 0x41 a 0x5A.
  • Se pueden usar los siguientes nombres de campo:
    • TITLE
    • VERSION
    • ALBUM
    • TRACKNUMBER
    • ARTIST
    • PERFORMER
    • COPYRIGHT
    • LICENSE
    • ORGANIZATION
    • DESCRIPTION
    • GENRE
    • DATE
    • LOCATION
    • CONTACT
    • ISRC
  • Aunque los campos anteriores son sugeridos, podemos usar los propios (sin abusar de ello) y por ejemplo, un campo llamado BEATPERMINUTE, es totalmente válido.
Una vez que hemos visto cómo esta estructurado los comentarios para los archivos OGG Vorbis, analicemos la función "ReadVorbisData()". La función "ReadVorbisData()" sirve para leer un bloque que contenga los datos para los comentarios de los archivos ogg (y FLAC). Recibe un arreglo del tipo "byte" que contiene el bloque completo con los datos.
Primero creamos las variables del tipo "uint" llamadas: "NumberOfFields", "CurrentField" y "CurrentPosition"; las inicializamos con los valores de cero, uno y cero respectivamente. La variable "NumberOfFields" servirá para almacenar la cantidad de comentarios contenidos en el bloque; la variable "CurrentField" llevará la cuenta de cual es el número de comentario que se procesa al momento; la variable "CurrentPosition" llevará la posición en la que nos ubicamos dentro de la secuencia.
Luego procedemos a leer los datos, si recordamos la descripción, lo primero que debemos de buscar es la secuencia que contiene el tamaño de la descripción del proveedor y deberían de ser los primeros cuatro bytes de la secuencia. Creamos una variable del tipo "uint" llamada "CurrentSize" y tal cómo su nombre los indica, nos servirá para ir almacenando el tamaño del comentario actual, procedemos a hacer la lectura y tendremos el tamaño de la cadena de texto siguiente, por lo que podemos desplazarnos en el bloque cuatro bytes, por lo que a la variable "CurrentPosition" la aumentaremos en cuatro. Para almacenar la cadena de texto que vamos a leer, creamos una nueva variable llamada "CurrentChunk" del tipo "byte[]" y reservaremos espacio de acuerdo al tamaño actual del comentario por lo que la asignamos el tamaño de "CurrentSize". Después copiamos del arreglo "TheData" (que es parámetro que se paso a la función) al arreglo "CurrentChunk" la cantidad de bytes marcada por "CurrentSize", creamos una nueva variable del tipo "string" llamada "CurrentText" y la asignaremos con el resultado de la llamada a la función "Encoding.UTF8.GetString()" con el parámetro "CurrentChunk".


Nuevamente nos desplazamos la cantidad de bytes que nos diga "CurrentSize" y nos encontraremos en la posición que contiene la cantidad de campos por la cual está compuesto el bloque por lo que asignamos la variable "CurrentText" con el resultado de mandar a llamar a la función "BitConverter.ToUInt32()". Ahora que tenemos este dato, crearemos un arreglo del tipo "string" y reservaremos tantos espacios cómo "NumberOfFields" nos diga.
Para leer los campos, simplemente crearemos un bucle que haga mas o menos lo mismo que hicimos ahorita: leeremos el tamaño, leemos la cadena de texto, desplazamos el marcador posición y repetimos hasta que leamos todos los campos. 
La función recibe un parámetro del tipo arreglo de "byte" y devuelve una un arreglo del tipo "string" que contiene todos los campos encontrados.

La siguiente función es "GetBlockType". Esta función lo que hace es "convertir" el valor de un solo byte a su equivalente a la descripción del tipo de bloque. Para hacerlo, simplemente desplazamos los bits del byte tres lugares a la izquierda y luego a la derecha, hay que recordad que el primer byte contiene el marcador por si es el ultimo bloque, pero además el tipo de bloque por lo que podríamos encontrar algo cómo sigue:10000110 , si lo desplazamos tres lugares a la izquierda, tendríamos: 00110000, y si lo desplazamos tres lugares a la derecha tendríamos 00000110 o seis decimal que marcaria un bloque del tipo "PICTURE". La función recibe un valor del tipo "byte" que es el byte que se va a convertir y regresa el resultado del desplazamiento.

La siguiente función es "IsLastFrame". Esta sirve para determinar si el bloque actual es el último (de acuerdo a la descripción), esta función es similar a la anterior, cómo queremos aislar solo el primer bit (de izquierda a derecha), simplemente desplazamos los bits siete lugares a la derecha y comprobamos, si el resultado del desplazamiento es uno, la función devuelve "true" en caso contrario devuelve false.

Las siguiente función es "GetBlockSize()" + 1 sobrecarga. Esta función se encarga de obtener el tamaño de un bloque. la primera recibe un parámetro y es un arreglo del tipo "byte" que contiene los datos. Para hacerlo, simplemente invertimos los datos del arreglo y regresamos el valor de mandar a llamar a la función "BitConverter.ToUInt32()". La sobrecarga recibe dos parámetros mas, el primero, un valor del tipo "int" que sirve cómo marcador de posición de donde se empezará a leer la secuencia de bytes, esta también se diferencia de la otra porque esta espera una secuencia de bytes "completa" o donde el primer grupo de cuatro bytes no es la secuencia que esperamos, por lo que es importante saber "donde empezar a leer", el tercer parámetro sirve para indica la longitud de bytes a leer. Ambas funciones devuelven un valor del tipo "uint" que representa el tamaño del bloque.

El último procedimiento es "GetBlock()" este sirve para ejecutar la funciones encargadas de buscar los bloques. recibe dos parámetros, el primero un valor del tipo "string" que es la ruta del archivo del cual se quiere obtener información. El segundo es un valor de la enumeración "BlockTypes" que sirve para indicar el tipo de bloque que se va a buscar. Esta función fue la primera que hice y al principio solo la hice para buscar la secuencia "fLaC" por lo que puede parecer repetitiva, pero igual lo deje para dar mayor claridad a lo hicimos.

Y bien, por ahora es todo, cómo de costumbre puedes descargar el proyecto completo de mi dropbox para que lo pruebes por ti mismo. Cabe resaltar que al igual cómo paso con los archivos MP3, aun falta mucho, pero algo que si haré en el corto plazo será crear un programa que agregue la caratulas de los discos.

Los leo luego.

Vamos a programar #103 - Leyendo metadatos de archivos FLAC (pt 2. Ver c#)

Hola de nuevo a todos. El día de hoy vamos a continuar con mas de los archivos FLAC y sus metadatos.

En el post anterior vimos cómo es que está estructurado cada bloque de metadatos, incluso vimos cómo es que se hace la lectura de cada uno (y leímos el bloque "STREAMINFO"). Ahora que sabemos cómo, solo nos queda automatizarlo y para eso vamos a usar el lenguaje de programación C#.
Para empezar, veamos el código (sin ningún orden particular en las funciones):


using System; using System.Drawing; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace FLACTagReader { public partial class Form1 : Form { // 0 : STREAMINFO 0000000 // 1 : PADDING 0000001 // 2 : APPLICATION 0000010 // 3 : SEEKTABLE 0000011 // 4 : VORBIS_COMMENT 0000100 // 5 : CUESHEET 0000101 // 6 : PICTURE 0000110 // 7-126 : reserved 1111000 - Invalid // 127 : invalid, to avoid confusion with a frame sync code /// <summary> /// Enumeración con los posibles bloques contenidos en un archivo FLAC /// </summary> private enum BlocksTypes { /// <summary> /// Bloque mandatorio con la información del Stream /// </summary> Block_Type_StreamInfo = 0, /// <summary> /// Bloque de Padding /// </summary> Block_Type_Padding = 1, /// <summary> /// Bloque con la informacion de la aplicación /// </summary> Block_Type_Application = 2, /// <summary> /// Bloque Seektable /// </summary> Block_Type_SeekTable = 3, /// <summary> /// Bloque con metadatos /// </summary> Block_Type_VorbisComment = 4, /// <summary> /// Bloque con cuesheet /// </summary> Block_Type_CueSheet = 5, /// <summary> /// Bloque con imagen /// </summary> Block_Type_Picture = 6, /// <summary> /// Bloque no válido /// </summary> Block_Type_NoValid = 7 } public Form1() { InitializeComponent(); } /// <summary> /// Crea una imagen a partir de una secuencia de bytes /// </summary> /// <param name="bytesArr">Matriz de bytes que contiene los datos de la imagen</param> /// <returns>Regresa una imagen</returns> public Image ByteArrayToImage(byte[] bytesArr) { using (MemoryStream memstr = new MemoryStream(bytesArr)) { Image img = Image.FromStream(memstr); return img; } } // 4 4 n*4 4 n*4 4 4 4 4 !4 !n*4 //<32>,<32>,<n*8>,<32>,<n*8>,<32>,<32>,<32>,<32>,<32>,<n*8> // 1 2 3 4 5 6 7 8 9 10 11 /// <summary> /// Lee todo los datos del bloque PICTURE y extrae la imagen contenida /// </summary> /// <param name="TheData">Arreglos bytes que contiene todo el bloque PICTURE</param> /// <returns>Regresa una imagen</returns> private Image ReadPictureData(byte[] TheData) { uint CurrentPos = 4;//1 uint CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4); CurrentPos += 4;//2 string MimeType = Encoding.ASCII.GetString(TheData, (int)CurrentPos, (int)CurrentSize); CurrentPos += CurrentSize;//3 CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4); CurrentPos += (CurrentSize + (4 * 5));//4-9 CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4); CurrentPos += 4; byte[] TempImage = new byte[CurrentSize]; Array.Copy(TheData, CurrentPos, TempImage, 0, CurrentSize); return ByteArrayToImage(TempImage); } /// <summary> /// Convierte un número en su valor equivalente al tipo de bloque /// </summary> /// <param name="Data"> /// Entero sin signo con la representacion del bloque</param> /// <returns>Regresa un valor de la enumeración <typeparamref name="BlockTypes"/>BlockTypes</returns> private BlocksTypes NumberToBlockType(uint Data) { switch (Data) { case 0: return BlocksTypes.Block_Type_StreamInfo; case 1: return BlocksTypes.Block_Type_Padding; case 2: return BlocksTypes.Block_Type_Application; case 3: return BlocksTypes.Block_Type_SeekTable; case 4: return BlocksTypes.Block_Type_VorbisComment; case 5: return BlocksTypes.Block_Type_CueSheet; case 6: return BlocksTypes.Block_Type_Picture; default: return BlocksTypes.Block_Type_NoValid; } } /// <summary> /// Lee el bloque con la informacion estructurada /// </summary> /// <param name="TheData">Arreglo de bytes del cual se extraera la información</param> /// <returns>Regresa un arreglo del tipo string con todos los campos que se encontraron</returns> private string[] ReadVorbisData(byte[] TheData) { uint NumberOfFields = 0; uint CurrentField = 1; uint CurrentPosition = 0; //Vendor uint CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition); CurrentPosition += 4; byte[] CurrentChunk = new byte[CurrentSize]; Array.Copy(TheData, CurrentPosition, CurrentChunk, 0, CurrentSize); string Currenttext = Encoding.UTF8.GetString(CurrentChunk); CurrentPosition += CurrentSize; NumberOfFields = BitConverter.ToUInt32(TheData, (int)CurrentPosition); CurrentPosition += 4; string[] Fields = new string[NumberOfFields]; //CommentField while (CurrentField <= NumberOfFields) { CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition); CurrentPosition += 4; byte[] CurrenUserCommentList = new byte[CurrentSize]; Array.Copy(TheData, CurrentPosition, CurrenUserCommentList, 0, CurrentSize); Currenttext = Encoding.UTF8.GetString(CurrenUserCommentList); CurrentPosition += CurrentSize; Fields[CurrentField - 1] = Currenttext; CurrentField += 1; } return Fields; } /// <summary> /// Obtiene el valor del bloque desde un byte /// </summary> /// <param name="Data">byte del cual se va a obtener la información</param> /// <returns>Regresa un valor que es equivalente al tipo de bloque</returns> private UInt32 GetBlockType(byte Data) { Data <<= 3; Data >>= 3; return Data; } /// <summary> /// Obtiene si el bloque es el último de la serie /// </summary> /// <param name="Data">byte del cual se obtendra la información</param> /// <returns>true si el bloque es el último, false en caso contrario</returns> private bool IsLastFrame(byte Data) { Data >>= 7; if (Data == 1) return true; else return false; } /// <summary> /// Obtiene el tamaño de un bloque /// </summary> /// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param> /// <param name="StartIndex">Indica la posicion en donde se empezará a leer</param> /// <param name="Size">Indica el tamaño en bytes que se van a leer</param> /// <returns>Regresa el tamaño del bloque actual</returns> private UInt32 GetBlockSize(byte[] Data, int StartIndex, int Size) { byte[] CurrentData = new byte[4]; Array.Copy(Data, StartIndex, CurrentData, 0, Size); Array.Reverse(CurrentData); return BitConverter.ToUInt32(CurrentData, 0); } /// <summary> /// Obtiene el tamaño de un bloque /// </summary> /// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param> /// <returns>Regresa el tamaño del bloque actual</returns> private UInt32 GetBlockSize(byte[] Data) { byte[] CurrentData = Data; //Ponemos el primer byte en 0 porque se usa para identificar el bloque //a la hora de convertir a UInt32 se esperan 4 bytes, pero este siempre será 0 u otro valor no relevante //para el tamaño del bloque CurrentData[0] = 0; Array.Reverse(CurrentData); return BitConverter.ToUInt32(CurrentData, 0); } /// <summary> /// Obtiene los datos de un bloque /// </summary> /// <param name="FileName">Nombre del archivo del cual se obtendra</param> /// <param name="TypeOfBlock">Tipo de bloque que se va a buscar</param> private void GetBlock(string FileName, BlocksTypes TypeOfBlock) { byte[] DataBuff; bool LastFrame = false; FileStream FS = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read); using (BinaryReader BR = new BinaryReader(FS, Encoding.ASCII)) { DataBuff = BR.ReadBytes(4); if (string.Equals(Encoding.ASCII.GetString(DataBuff), "fLaC", StringComparison.Ordinal)) { UInt32 CurrentBlockSize; UInt32 CurrentPosition = (UInt32)FS.Position; BlocksTypes CurrentBlockType = BlocksTypes.Block_Type_NoValid; while (!LastFrame) { //primer bloque siempre será Block_Type_StreamInfo DataBuff = BR.ReadBytes(4); LastFrame = IsLastFrame(DataBuff[0]); CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0])); if (CurrentBlockType == TypeOfBlock) { if (TypeOfBlock == BlocksTypes.Block_Type_VorbisComment) { string[] Fields; CurrentBlockSize = GetBlockSize(DataBuff); Fields = ReadVorbisData(BR.ReadBytes((int)CurrentBlockSize)); textBox1.Clear(); for (int i = 0; i < Fields.Length; i++) { textBox1.Text += Fields[i] + " | "; } } else if (TypeOfBlock == BlocksTypes.Block_Type_Picture) { CurrentBlockSize = GetBlockSize(DataBuff); pictureBox1.Image = ReadPictureData(BR.ReadBytes((int)CurrentBlockSize)); } } else { CurrentBlockSize = GetBlockSize(DataBuff); FS.Seek(FS.Position + CurrentBlockSize, SeekOrigin.Begin); } } } else { MessageBox.Show("Archivo Flac no válido"); } } } private void button1_Click(object sender, EventArgs e) { using (OpenFileDialog OpDiag = new OpenFileDialog()) { OpDiag.Filter = "Archivos FLAC|*.flac|Todos los arhivos|*.*"; if (OpDiag.ShowDialog() == DialogResult.OK) { GetBlock(OpDiag.FileName, BlocksTypes.Block_Type_VorbisComment); GetBlock(OpDiag.FileName, BlocksTypes.Block_Type_Picture); pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage; } } } } }
Lo primero que hacemos es crear una enumeración llamada "BlockTypes" que contiene las posibles enumeraciones para cualquier tipo de bloque. Si bien la especificación dice que los valores de 0 hasta 127 son validos (aunque no tienen uso todos, al menos  al momento de escribir esto), decidí declarar cómo no validos a los que superan al 6 y por eso, después de ahí, su nombre es "Block_Type_NoValid". Se podrían usar solo números, pero me pareció mas fácil de identificar cada uno de esta forma.

La primera función es "ByteArrayToImage()" del tipo "Image". Esta función, cómo su nombre lo pudiera indicar, se encarga de convertir una secuencia de bytes en una imagen (si los datos son válidos), recibe cómo parámetro un arreglo del tipo "byte[]" que contiene la secuencia de bytes de la imagen. Regresa un valor del tipo "Image" que es la imagen dentro del archivo (si es que hay).

La siguiente función es "ReadPictureData()". Esta función es la encargada de procesar los datos del bloque  "PICTURE". Recibe cómo parámetro un arreglo del tipo "byte[]" que contiene la secuencia de bytes del bloque "PICTURE" completo. Para procesarlo, se siguen las siguientes:
  • <32> El tipo de imagen de acuerdo a la descripción de la etiqueta ID3 "APIC".
  • <32> El tamaño de la descripción del tipo de archivo o MIME type.
  • <n*8> Cadena de texto con la descripción del tipo de archivo.
  • <32> El tamaño de la descripción de la imagen.
  • <n*8> La descripción de la imagen en UTF8
  • <32> El ancho de la imagen en píxeles
  • <32> El alto de la imagen en pixeles.
  • <32> La profundidad del color de la imagen en bits por pixel.
  • <32> Para las imágenes con índice de color, el número de colores usados, para el resto 0.
  • <32> El tamaño de los datos de la imagen.
  • <n*8> Los datos de la imagen.
Para realizar la lectura de la imagen se asume que solo se pasan los datos del bloque completos, solo omitiendo el tamaño del bloque mismo. Y en este caso, cómo solo nos interesa obtener la imagen "tal cual" solo nos concentraremos en leer las ultimas dos secuencias, pero en el código lo hacemos paso a paso para poder apreciar mejor lo que esta ocurriendo y para simplificarlo, lo podemos apreciar mejor en el siguiente diagrama:


Todo este bloque se representa en 11 partes (parte de abajo de la imagen), cada una de la partes ya tiene su tamaño en bits establecido (parte media), que es lo mismo en bytes (parte superior). Si bien hace un momento mencione que vamos a ignorar todo salvo la última y penúltima parte, no podemos ignorar del todo a los otros datos, sobre todo a los que son variables. Ahora vamos leyendo el código.
Primero creamos una variable del tipo "uint" llamada "CurrentPos", esta la vamos a ir usando para saber en que parte del bloque estamos, si observaste bien, te darás cuenta que esta inicializada con un valor de 4, al hacer esto, estaremos brincando la primer secuencia de 32 bits 

Seguido, creamos una variable llamada "CurrentSize" del tipo "uint" y la asignamos con el valor de la llamada a la función "GetBlockSIze()" (que veremos pronto), con esto tendremos el tamaño de la siguiente secuencia (3) por lo que podemos avanzar 4 bytes mas el tamaño que nos dio con la función anterior 
.
Aunque C# nos da muchas facilidades, a la hora de tratar de hacer lo mismo en otro lenguaje las cosas se pueden complicar, este bloque es una cadena de texto con la descripción del tipo de archivo por lo que es importante tenerlo en cuenta, para eso creamos una variable llamada "MimeType" del tipo "string" y se le asigna el valor de la llamada a la función "Encoding.ASCII.GetString()", esta recibe tres parámetro, el primero, un arreglo del tipo "byte[]" con los datos de la cadena ASCII que esperamos obtener; el segundo es un valor del tipo "int" que sirve cómo indicador de que parte del arreglo es donde se va a empezar a leer; el tercero es un valor del tipo "int" que sirve para indicar cuantos bytes de la secuencia se van a leer. Al realizar la "conversión" de una secuencia de "bytes" a "string", tendremos algo cómo lo de la imagen que sigue:

Luego sumamos la posición actual y el tamaño de la secuencia anterior. Para las dos siguientes partes, repetimos lo mismo: obtenemos el tamaño del bloque, lo sumamos a la posición actual y avanzamos cuatro bytes mas.

Las siguientes 4 secuencias son iguales en tamaño, por lo que podemos brincar 4*4 (en el código es 4*5 porque brinque la secuencia anterior tambien).

Una vez leído las partes anteriores, solo queda leer los datos de la imagen por lo que repetimos nuevamente: obtenemos el tamaño de los datos de la imagen, creamos una nueva variable del tipo "byte[]" llamada "TempImage" y al mismo tiempo le asignamos el tamaño de la secuencia actual, luego copiamos la porción del bloque al arreglo que acabamos de crear y devolvemos el resultado de mandar a llamar la función "ByteArrayToImage()".

Y bien, por ahora es todo, en el siguiente post publicaré el proyecto completo y terminaremos de revisar las funciones que no vimos.

Los leo luego.

Vamos a programar #102 - Leyendo metadatos de archivos FLAC (pt.1)

 Hola de nuevo a todos. El día de hoy vamos a continuar con más de los archivos FLAC.


En los post mas recientes hemos visto las cualidades de los archivos FLAC, pero además cómo crear los nuestros. Al igual que en MP3, existen metadatos embebidos en ellos, y sirven para describir algunos datos importantes tales cómo: el nombre de la canción, el artista, el nombre del álbum, el genero, etc.


Estructura de un archivo FLAC

La estructura de los archivos FLAC es totalmente diferente a la de un MP3 por lo que vale la pena revisarla por separado. Cada archivo FLAC consiste de cuatro partes principales:

  • Una cadena de cuatro bytes con la secuencia "fLaC" (así tal cual se ve).
  • Un bloque de metadatos con la información del "stream"
  • Cero o mas bloques de metadatos.
  • Uno o mas "frames" de audio.

Para mas detalles se puede revisar la página de xiph.org, donde esta la descripción completa para los archivos FLAC.

De las cuatro partes que se mencionan anteriormente, nos concentraremos en la segunda y tercera (Técnicamente las dos entran en el mismo conjunto, solo que la segunda es obligatoria, mientras que las subsecuentes son opcionales, hablando solamente de los bloques de metadatos), por lo que revisaremos cómo es que está compuesta cada una.

Pero antes una representación de cómo está estructurado el archivo:


Si observamos bien la imagen, veremos que hay dos partes centrales: "STREAM" y "FRAME", por ahora revisaremos la parte de "STREAM" que contiene los datos que nos interesan. 

Metadatos

A partir de ahora nos referiremos a cada parte cómo "bloque". Del lado de METADATA, cada bloque estará constituido por un encabezado (HEADER) y el bloque de datos en si, el encabezado se encarga de describir cómo es que cada parte esta formada. Para poder leer cada bloque, deberemos de seguir los datos de este. Salvo que se indique lo contrario, cada valor N representará un solo bit (OJO: bit, no byte al menos que se indique lo contrario), al igual que en la documentación (que recomiendo encarecidamente leer dos veces), usaremos un número entre los corchetes cuadrados "<" y ">" El valor que se encuentre en medio dirá cuantos bits conforman esa parte. Habiendo aclarado lo anterior, veamos cómo se forma el encabezado de cada bloque:
  • STREAM
  • METADATA_BLOCK
  • METADATA_BLOCK_HEADER (Encabezado)
    • <1>   Indicador de último bloque
    • <7>   Descriptor del tipo de bloque
    • <24> Tamaño del bloque en bytes (BYTES!!!)
Para el indicador del último bloque se usa un solo bit, si es 0 indica que hay mas bloques a continuación, pero si es 1, indica que el bloque es el último y empiezan los "FRAMES" por lo que si solo estamos leyendo los metadatos, deberíamos de dejar de buscar.

La secuencia de los siguientes 7 bits, indican que tipo de bloque es el que sigue a continuación, para ello hay una serie ya predefinida y dependiendo del valor que este nos diga, el bloque del tipo será. Actualmente solo hay siete tipos de bloque ya predefinidos y son los que siguen:
  • 0: STREAMINFO
    • Contiene información acerca de todo el archivo, este bloque es mandatorio y siempre será e primero de la secuencia después de la secuencia 0x66, 0x4c, 0x61, 0x43 (o de manera simple después de  "fLaC" ).
  • 1: PADDING
    • Este tipo de bloque sirve cómo reserva y el contenido no representará nada, normalmente se llena con 0x00. Cuando se crea el archivo sin los metadatos completos, en lugar des escribir un archivo nuevo con los bloques nuevos, simplemente se reemplaza este bloque conservando el archivo original y agilizando la escritura del mismo.
  • 2: APPLICATION
    • Nombre de la aplicación que se uso para codificar el archivo, puede ser cualquiera que el usuario elija, aunque se recomienda el registro de la aplicación para obtener un ID único para cada quien.
  • 3: SEEKTABLE
    • Sirve cómo marcador para desplazarse en el archivo a la hora de la reproducción, aunque se puede omitir, se recomienda incluirlo para agilizar la lectura.
  • 4: VORBIS_COMMENT
    • Este bloque contiene la información relevante a los creadores del contenido multimedia y la descripción del mismo, se usa la especificación de "Vorbis comment"
  • 5: CUESHEET
    • Contiene información que se puede usar en un CD
  • 6: PICTURE
    • Imagen representativa del archivo, similar a la imagen embebida en un archivo MP3.
  • 7-126: Reservado
    • Reservado por ahora, por lo que se podría decir que si un bloque tiene descriptor en este rango, no es valido. Se planea su uso futuro.
  • 127: No válido
    • Bloque no valido, ya que la misma secuencia se usa cómo marcador para sincronización.
Por lo que por ahora solo nos serian relevantes los bloques con descriptores: 0, 4, 6, 7-126 y 127. Especialmente el que tenga descriptor 0, porque es mandatorio; 4, porque seria el equivalente a los TAGS en mp3; y 6, porque es la imagen del álbum (por lo general). Sabiendo los tipos de bloques que vamos a leer, vamos a concéntranos solo en ellos, por lo que primero revisaremos cómo es que está armado el bloque "STREAMINFO".

Bloque "STREAMINFO"

El bloque STREAMINFO, consta de la siguiente secuencia:
  • <16> Tamaño mínimo del bloque.
  • <16> Tamaño máximo del bloque.
  • <24> Tamaño mínimo del "FRAME" en bytes.
  • <24> Tamaño máximo del "FRAME" en bytes.
  • <20> Frecuencia de sampleo.
  • <3> Número de canales.
  • <36> Total de muestras por segundo.
  • <128> MD5 del audio.
Ya que cada una de las secciones tiene un tamaño ya predefinido, podemos asegurar que toda la secuencia siempre tendrá el mismo tamaño y cómo no es necesario que sepamos el contenido de esta, bastará con desplazarnos al siguiente bloque. Antes de continuar, haremos el calculo a mano de la posición en la que empieza y en la que acaba, para eso usaremos XVI32 para ver el contenido de forma hexadecimal, y el archivo de muestra será el que usamos para comparar en el post de "FLAC vs WAV"

Si leemos las cosas en orden, debemos de empezar por buscar la secuencia "fLaC" que serán los primeros 4 bytes.

Ahora que sabemos que el primer bloque debe de ser el bloque STREAMINFO, nos vamos a concentrar en eso, si hacemos memoria, lo primero que debemos de buscar de cada bloque, es su encabezado que serán 32 bits (4 bytes) por lo que inmediatamente después de la secuencia "fLaC" los siguientes bytes corresponden al encabezado de nuestro bloque.

En este caso, el encabezado está representado por la secuencia hexadecimal: 0x00, 0x00, 0x00 y 0x22. ¿Pero cómo la interpretamos? simple, la referencia dice que el primer bit es el indicador del último bloque, solo si es 1, para entender mejor, veamos el número en binario.

Cómo iremos leyendo "de byte en bytes", el mínimo de bits a leer es 8, pero sim problema podemos aislar el primer bit de cada byte y determinar si es o no el último bloque, en este caso, cómo encontramos un 0, indica que hay mas bloques después de este.

Los siguientes 7 bits indicaran el tipo de bloque y para el tipo de bloque debe de ser 0, antes veamos cómo son los números del uno al seis en binario (recuerda que en el blog tenemos DecToAny que sirve para convertir un número decimal a cualquier base hasta 25, puedes descargar la aplicación para Windows, android o usar la versión web).


Pero si no quieres molestarte en revisar, aquí la numeración binaria del cero al seis, que son los valores validos para esta parte de la secuencia.



Cuando leemos la información del bloque encontramos con 0x00 lo que es igual cero decimal o 0 en binario, lo que confirma que el bloque es el bloque STREAMINFO. Los restantes 24 bits indican cual es el tamaño del bloque. Al igual que los hicimos con el primer byte, los siguiente 3, se irán leyendo de uno por uno, pero juntos representan un solo número.

Toda la secuencia anterior representa 34

Si en XVI32 marcamos esos 34 bytes, tendríamos seleccionado todo el bloque.

Y bien, por ahora es todo. para no hacer muy extenso el post, terminaremos por hoy, sirve que da tiempo para leer toda la documentación del los archivos FLAC.
Si bien este post se podría tomar cómo una mala copia de la documentación, no lo es y estoy seguro que a mas de uno le será de ayuda, pero además así es nuestro estilo de trabajo, primero nos ensuciamos las manos descuartizando el archivo con el lector hexadecimal y luego hacemos código que hace lo mismo pero más rápido. Ahora y diferente al caso de ArtView, trataré de hacer versiones para los distintos lenguajes de programación.

Ya es diciembre por lo que publicaré un par de post antes de que acabe el año. Es cierto que la actividad del blog vino en picada, pero para todos aquellos que se quedaron o que regresaron, los post iran saliendo de manera regular.

Los leo luego.

El Video (AUDIO) Correcto #9 - Convirtiendo CD a FLAC

 Hola de nuevo a todos, el día de hoy vamos a ver cómo extraer nuestros audios de CD's originales y convertirlos a FLAC. Al inicio pensé en hacer alugan clase de programa para hacerlo, pero cómo probablemente solo uno de cada mil personas va a convertir sus discos (incluso a estas alturas, habrá quienes no los conozcan), decidí solo mostrar cual es el método que uso actualmente.


Para poder hacer la copia de los discos, vamos a usar MediaGo de SONY, aunque es un programa relativamente viejo, aun al día de hoy funciona y tiene la virtud de que las funciones que requieren de internet, aun funcionan. Para no adelantarnos, vamos a ir en orden y lo primero que haremos es descargar el instalador de Media Go. Una vez lo descargamos, lo instalamos y lo ejecutamos.

Una vez que el programa está listo, simplemente insertaremos nuestro CD. De lado izquierdo aparecerá en una ficha llamada "CD de audio"; hacemos clic en ella y podremos ver el contenido del CD.


Antes de continuar, haremos clic en el menú "Herramientas>>Preferencias" y se abrirá un dialogo con los ajustes del programa.


Hacemos clic en "Importación de CD" y en la caja de selección de la opción "Usar pistas usando el formato" seleccionaremos la opción FLAC. Hacemos clic en aceptar y los ajustes estarán listos.

Para comenzar la copia, simplemente hacemos clic en el botón "Importar CD". La copia comenzará y al finalizar, podremos escuchar nuestro archivos.


La razón principal para usar "Media Go" es simple: Ya tiene todo lo necesario; cuando importa los archivos, se conecta a la base de datos Gracenote y obtiene todos los datos que puedan ser relevantes a la hora de identificar una canción.

Y bien, por ahora es casi todo...

He hablado de las cosas que son necesarias para poder oír audio en buena calidad (y no hablo de esos "audiofilos mugorosos" que le rebanan los bordes al los CD's para mejorar la calidad del audio), ya mencione los audifonos, los archivos de audio y la última cosa que es importante mencionar; y eso es el dispositivo que se va a encargar de reproducir el archivo.

Muchos de los celulares, simplemente contienen la "placa de audio" mas genérica que se pude encontrar, aun en los dispositivos de gama media, la mayor parte de ellos tendrán dicha placa que solo es capaz de reproducir hasta 32000Hz (si bien nos va); aunque parezca redundante; si conectamos unos audífonos de calidad superior al puerto 3.5mm del teléfono y este solo pude decodificar hasta cierta calidad, estaremos desperdiciando el hardware. Identificar si un teléfono es capaz de reproducir audio en alta calidad no es sencillo, ya que muchas veces es un dato que no es relevante a la hora de vender (siempre se verá primero la RAM y el CPU) pero si es posible, buscar el logotipo "Hi res audio" ayuda.


Aun si el equipo no cuenta con el logotipo, hay otras formas de saber si lo es.

LDAC ayuda a transmitir audio en alta calidad por Bluetooth


Y bien, por ahora es todo, en el siguiente post veremos cómo leer los metadatos de los archivos FLAC en C# y varios lenguajes mas.

Los leo luego.


El Video (AUDIO) Correcto #8 - WAV VS FLAC

 Hola de nuevo a todos, el día de hoy vamos a continuar con un poco más sobre el audio de buena calidad.



En el post anterior vimos cómo es importante tener un par de audífonos de la calidad adecuada para poder disfrutar de la música cómo es debido. Pero, no solo se trata de los audífonos, hay otros elementos que son importantes a la hora de disfrutar del audio de buena calidad. 

En este post nos concentraremos en el archivo de audio en si. En reiteradas ocasiones he dicho que los archivos FLAC ofrecen una calidad superior al MP3, incluso en alguno de los post anteriores hice el comparativo, en twitter (o ahora X), mucha gente dijo que los archivos FLAC si bien tienen una calidad mejor que el MP3, también se pierde mucha de la información que conforma la música, y esto es cierto o no, dependiendo de cómo se configura la compresión, cual es el origen del cual vamos a crear nuestro FLAC y algunos otros factores. Pero por lo general, si se configura bien, los archivos FLAC no tendrán ninguna perdida y para probarlo, vamos a hacer la misma prueba que hicimos hace tiempo; vamos a comparar FLAC contra WAV. ¿Por qué WAV? te estarás preguntando, simple, es el contenedor que se usa para el audio sin compresión, por lo que su tamaño será significativamente mayor al de los otros.

Archivo WAV copiado desde un CD ORIGINAL

Archivo FLAC creado desde un CD ORIGINAL

Las pruebas.

Al igual que la vez anterior, vamos a usar AUDACITY para comparara los audios. Su interfaz a cambiado un poco, pero si sigues los pasos que te mostraré, todo saldrá igual. En esta ocasión de igual forma subiré dos archivos de audio de la misma canción, pero eso si, recuerda que si no posees un CD original, debes de eliminarlo a las 24 horas máximo (resulta difícil tener una copia original, aun buscando el ID del CD que es: "Fantasía TFF-3838" no se encuentra, ni en la pagina de fonovisa que es la que lo editó).


  1. Descargar audacity.
  2. Descargar el archivo WAV.
  3. Descargar el archivo FLAC.
Una vez instalado audacity, lo abriremos luego arrastraremos los archivos de audio que descargamos,
puede ser uno o los dos a la vez. Una vez abiertos, seleccionaremos una pista de audio (de hecho dos porque son estéreo) e iremos al menú "Efectos>>Especial>>Invertir" con esto crearemos una versión invertida de nuestra canción. Para "sumarlas", bastara con seleccionar las dos pistas (técnicamente cuatro pistas porque ambas son estéreo) para hacerlo, haremos clic en el panel izquierdo y muestras mantenemos presionada la tecla "control" hacemos lo mismo con la otra. Una vez seleccionada las dos pistas, iremos al menú "Pistas>>Mezclar y generar nueva pista" y con esto se generará una nueva que se vera cómo una línea. En esta ocasión no es necesario alinear los inicios de las pistas puesto que eso ya lo hice (nuevamente puedes revisar el post donde comparé MP3 y FLAC).

Cómo el archivo resultante es "silencio" podemos afirmar que ambas pistas son iguales, solo que, la ventaja del archivo FLAC es que pesa casi la mitad que el archivo WAV.

Y bien, por ahora es todo, en el siguiente post ahora si veremos cómo importar nuestros discos al formato FLAC.

Los leo luego.

El Video (AUDIO) Correcto #7 - Un poco mas sobre el audio.

 Hola de nuevo a todos, el día de hoy vamos a ver un poco mas sobre los archivos FLAC.

imagen tomada de: https://www.revistagadgets.com/2017/04/04/audio-hi-res-la-nueva-linea-audifonos-pioneer/


Cómo muchos de ustedes sabrán, me gusta mucho oír música (música de verdad y no "regeton"), pero siempre había usado equipo de "baja calidad" (y solo por nombrarlo de alguna manera). Pero no hace mucho tiempo, visité un lugar donde se exponían diferente sistemas de audio, desde los mas sencillos; cómo bocina bluetooth; hasta los mas complejos; llámese audio de estudio.


Dentro del mismo lugar había un apartado para los audífonos, de inmediato fui a revisar, y había varios para probar. Cada uno tenia la ficha que describía cuales eran sus características, pero hacían un fuerte énfasis en la frecuencia de respuesta de cada uno.


Primero probé los que ofrecían una respuesta de 20 hasta 20000Hz, justo cómo la mayor parte de audífonos son, la calidad era aceptable, mucho de los audífonos que he tenido cumplían con esa frecuencia y todo parecía aceptable. Los siguientes tenían una respuesta de 20 hasta 32000Hz, la misma calidad que los audífonos que siempre usaba, y justo cómo los anteriores, todo funcionaba bien, y a mi gusto, nada podía superar la calidad. En la siguiente mesa había unos audífonos con una respuesta de 20 hasta 40000Hz... había vivido engañado todo el tiempo, siempre me habían dicho que el oído humano no era capaz de oír frecuencias mas allá de los 32000Hz, pero al momento de probar esos, de inmediato me di cuenta que, si bien es cierto que el oído humano está limitado a ciertas frecuencias, el hecho es que cada persona es diferente y hay quienes alcanzan un rango auditivo mayor al de otros. En mi caso es muy difícil decir hasta "donde puedo escuchar", ya que nunca he hecho un estudio que sirva para eso, pero no había duda de que esos audífonos "sonaban mejor" que los otros, por lo que decidí conseguir unos.


Finalmente cosegui unos audifonos edifier w820nb plus que cuestan poco y tienen la calidad superior a los que ya tenía. 

Pero para poder disfrutar de un buen audio, no solo basta con tener los audífonos, también hay que tener un dispositivo compatible y los archivos de audio adecuados. Por fortuna para mi, aun soy de la vieja escuela y tengo mi colección de discos guardados. Normalmente lo que hacia era utilizar mi reproductor de CD (si, en 2023 aun sigo usándolo), pero el inconveniente que encontré, es que la única salida que tiene, es el plug tradicional de 3.5 mm, mientras que los audífonos soportan bluetooth y USB-C.

Para poder escuchar mis discos decidí crear una copia  de ellos, pero no en mp3, porque ya en ocasiones anteriores he hablado de ciertas desventajas que este tiene con respecto a FLAC, detalles que puedes encontrar en un post que hice hace un buen rato.




 Y bien, por ahora es todo, en los siguientes post veremos detalles de cómo importar la música en FLAC, pero además entraremos en detalles técnicos para poder leer y editar los metadatos de los archivos; para mantener todo mas o menos organizados (al igual que lo hicimos con MP3).

Los leo luego.