Статьи

Xuggler Tutorial: Транскодирование и модификация медиа

Примечание. Это часть нашей серии « Учебники по разработке Xuggler ».

В моем предыдущем уроке я провел краткое введение в Xuggler для управления видео . В этой части мы рассмотрим некоторые более интересные возможности, предоставляемые Xuggler и FFmpeg , такие как транскодирование видео и модификация медиа. Не забывайте, что Xuggler — это библиотека Java, которую можно использовать для распаковки, обработки и сжатия записанного или живого видео в реальном времени.

Xuggler предлагает два разных API программирования, которые можно использовать для одной и той же цели. Во-первых, у нас есть MediaTool API :

MediaTool — это простой интерфейс прикладного программирования (API) для декодирования, кодирования и изменения видео в Java. MediaTool скрывает многие мелкие детали контейнеров, кодеков и т. Д., Чтобы вы могли сосредоточиться на носителе, а не на инструментах. Тем не менее, MediaTool по-прежнему предоставляет доступ к базовым объектам Xuggler, поэтому вы можете точно контролировать зерно, если вам это нужно.

И еще есть Xuggler Advanced API , который позволяет вам вникать в детали манипуляции с видео, но добавляет уровень сложности.

Для начала мы будем использовать MediaTool API, а в последующих уроках мы также будем иметь дело с Advanced API.

Давайте начнем с перекодирования медиа из одного формата в другой. Транскодирование — это прямое цифро-цифровое преобразование одного кодирования в другое. Обычно это делается в тех случаях, когда целевое устройство не поддерживает формат или имеет ограниченную емкость хранилища, которое требует уменьшенного размера файла, или для преобразования несовместимых или устаревших данных в более поддерживаемый или современный формат. Транскодирование обычно представляет собой процесс с потерями , где сжатие с потерями — это метод кодирования данных, который отбрасывает (теряет) некоторые данные, чтобы достичь своей цели, в результате чего декомпрессия данных дает контент, отличный от исходного, хотя достаточно похожи, чтобы быть полезными в некотором роде.

Давайте посмотрим код высокого уровня для транскодирования, и я объясню детали позже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.javacodegeeks.xuggler;
 
import com.xuggle.mediatool.IMediaReader;
import com.xuggle.mediatool.IMediaViewer;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;
 
public class TranscodingExample {
 
    private static final String inputFilename = "c:/myvideo.mp4";
    private static final String outputFilename = "c:/myvideo.flv";
 
    public static void main(String[] args) {
 
        // create a media reader
        IMediaReader mediaReader =
               ToolFactory.makeReader(inputFilename);
         
        // create a media writer
        IMediaWriter mediaWriter =
               ToolFactory.makeWriter(outputFilename, mediaReader);
 
        // add a writer to the reader, to create the output file
        mediaReader.addListener(mediaWriter);
         
        // create a media viewer with stats enabled
        IMediaViewer mediaViewer = ToolFactory.makeViewer(true);
         
        // add a viewer to the reader, to see the decoded media
        mediaReader.addListener(mediaViewer);
 
        // read and decode packets from the source file and
        // and dispatch decoded audio and video to the writer
        while (mediaReader.readPacket() == null) ;
 
    }
 
}

С помощью нескольких строк кода мы можем преобразовать входной файл MPEG-4 в файл FLV . Мы начнем с создания IMediaReader, который используется для чтения и декодирования медиа. Он открывает медиа-контейнер, считывает из него пакеты, декодирует данные и затем отправляет информацию о данных в любые зарегистрированные объекты IMediaListener . Это где класс IMediaWriter вступает в игру. Он кодирует и декодирует медиа, обрабатывая как аудио, так и видео потоки. Чтобы сделать вещи более интересными, мы также прикрепляем IMediaViewer к нашему читателю. Это используется в качестве инструмента отладки, позволяя нам просматривать видео во время его декодирования. Кроме того, мы представляем различные статистические данные в процессе. Обратите внимание, что этот класс находится в экспериментальном режиме, что означает, что в нем есть некоторые ошибки, из-за которых он может зависнуть, поэтому обращайтесь с ним осторожно

По сути, с помощью приведенного выше кода мы присоединяем наших двух слушателей, IMediaWriter и IMediaViewer , к нашему объекту IMediaReader и обрабатываем обратные вызовы, пока читатель читает и декодирует пакеты из исходного файла. Это выполняется в цикле «время». Если мы запустим приложение с образцом входного файла, нам будет представлен экран, подобный следующему:

После того, как процесс будет завершен (он будет длиться столько же, сколько исходный видеофайл, так как мы одновременно его видим в режиме реального времени), будет создан новый выходной файл в формате FLV.

Давайте используем Ffmpeg из командной строки для сравнения входных и выходных файлов:

ffmpeg.exe -ic: /myvideo.mp4
Кажется, частота кадров кодека потока 1 отличается от частоты кадров контейнера: 59,92 (14981/250) -> 29,96 (14981/500)
Введите # 0, mov, mp4, m4a, 3gp, 3g2, mj2 из ‘c: /myvideo.mp4’:
Метаданные:
major_brand: mp42
minor_version: 0
compatibility_brands: isomavc1mp42
Длительность: 00: 04: 20,96, старт: 0,000000, битрейт: 582 кбит / с
Поток # 0.0 (und): Аудио: aac, 44100 Гц, стерео, s16, 115 кбит / с
Поток # 0.1 (und): Видео: h264, yuv420p, 480 × 270 [PAR 1: 1 DAR 16: 9], 464 кбит / с, 29,96 к / с, 29,96 тбр, 29962 тбн, 59,92 тбк

В исходном видеофайле контейнером является MPEG-4, и есть два потока: аудиопоток с использованием AAC на частоте 44100 Гц и видеопоток с использованием H.264 .

ffmpeg.exe -ic: /myvideo.flv
Кажется, что частота кадров кодека потока 0 отличается от частоты кадров контейнера: 1000,00 (1000/1) -> 29,97 (30000/1001)
Введите # 0, flv, из ‘c: /myvideo.flv’:
Метаданные:
продолжительность: 261
ширина: 480
высота: 270
видеодатарате: 62
частота кадров: 30
Видеокодек: 2
аудиодатарат: 62
Аудиосэмплирование: 44100
размер аудиосэмпла: 16
стерео: правда
аудиокодек: 2
Размер файла: 43117478
Длительность: 00: 04: 20,98, старт: 0,000000, битрейт: 128 кбит / с
Поток # 0.0: видео: flv, yuv420p, 480 × 270, 64 кбит / с, 29,97 тб, 1 тыс тб, 1 т тбк
Поток # 0.1: Аудио: mp3, 44100 Гц, 2 канала, s16, 64 кбит / с

После транскодирования сгенерированный видеофайл Flash использует видеопоток FLV и аудиопоток MP3 .

Теперь мы готовы изменить медиа-файл с помощью Xuggler. Но прежде чем писать код, нам нужно понять, как работает MediaTool :

MediaTool использует парадигму слушателя событий. Устройство записи автоматически добавляется в качестве «слушателя» к устройству чтения и получает все декодированные носители. Интерфейсы IMediaViewer и IMediaWriter (что на самом деле представляют собой средство просмотра и записи) реализуют интерфейс IMediaListener и могут быть добавлены в качестве прослушивателей для IMediaReader.

Мы подтвердили это на нашем предыдущем примере. Дело в том, что для выполнения различных модификаций входного файла нам необходимо настроить «медиа-конвейер». Мы создаем пользовательские реализации IMediaTool, а затем последовательно настраиваем прослушиватели для каждого инструмента, чтобы они передавали данные от одного к другому.

Предположим, мы хотим добавить статичное изображение к нашему видео и в то же время уменьшить громкость звука. В этом случае мы создаем два пользовательских объекта IMediaTool :

  • StaticImageMediaTool: делает видеоизображение и печатает файл статического изображения в определенном месте на экране.
  • VolumeAdjustMediaTool: регулирует громкость с постоянным коэффициентом.

Кроме того, мы создаем объект IMediaWriter, который будет использоваться для создания выходного файла. Со всем этим мы создаем цепочку, которая выглядит следующим образом:

читатель -> addStaticImage -> ReduveVolume -> писатель

Давайте посмотрим код, который реализует все вышеперечисленное:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.javacodegeeks.xuggler;
 
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ShortBuffer;
 
import javax.imageio.ImageIO;
 
import com.xuggle.mediatool.IMediaReader;
import com.xuggle.mediatool.IMediaTool;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.MediaToolAdapter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.mediatool.event.IAudioSamplesEvent;
import com.xuggle.mediatool.event.IVideoPictureEvent;
 
public class ModifyMediaExample {
 
    private static final String inputFilename = "c:/myvideo.mp4";
    private static final String outputFilename = "c:/myvideo.flv";
    private static final String imageFilename = "c:/jcg_logo_small.png";
 
    public static void main(String[] args) {
 
        // create a media reader
        IMediaReader mediaReader = ToolFactory.makeReader(inputFilename);
         
        // configure it to generate BufferImages
        mediaReader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);
 
        IMediaWriter mediaWriter =
               ToolFactory.makeWriter(outputFilename, mediaReader);
         
        IMediaTool imageMediaTool = new StaticImageMediaTool(imageFilename);
        IMediaTool audioVolumeMediaTool = new VolumeAdjustMediaTool(0.1);
         
        // create a tool chain:
        // reader -> addStaticImage -> reduceVolume -> writer
        mediaReader.addListener(imageMediaTool);
        imageMediaTool.addListener(audioVolumeMediaTool);
        audioVolumeMediaTool.addListener(mediaWriter);
         
        while (mediaReader.readPacket() == null) ;
 
    }
     
    private static class StaticImageMediaTool extends MediaToolAdapter {
         
        private BufferedImage logoImage;
         
        public StaticImageMediaTool(String imageFile) {
             
            try {
                logoImage = ImageIO.read(new File(imageFile));
            }
            catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException("Could not open file");
            }
             
        }
 
        @Override
        public void onVideoPicture(IVideoPictureEvent event) {
             
            BufferedImage image = event.getImage();
             
            // get the graphics for the image
            Graphics2D g = image.createGraphics();
             
            Rectangle2D bounds = new
              Rectangle2D.Float(0, 0, logoImage.getWidth(), logoImage.getHeight());
 
            // compute the amount to inset the time stamp and
            // translate the image to that position
            double inset = bounds.getHeight();
            g.translate(inset, event.getImage().getHeight() - inset);
 
            g.setColor(Color.WHITE);
            g.fill(bounds);
            g.setColor(Color.BLACK);
            g.drawImage(logoImage, 0, 0, null);
             
            // call parent which will pass the video onto next tool in chain
            super.onVideoPicture(event);
             
        }
         
    }
 
    private static class VolumeAdjustMediaTool extends MediaToolAdapter {
         
        // the amount to adjust the volume by
        private double mVolume;
         
        public VolumeAdjustMediaTool(double volume) {
            mVolume = volume;
        }
 
        @Override
        public void onAudioSamples(IAudioSamplesEvent event) {
             
            // get the raw audio bytes and adjust it's value
            ShortBuffer buffer =
               event.getAudioSamples().getByteBuffer().asShortBuffer();
             
            for (int i = 0; i < buffer.limit(); ++i) {
                buffer.put(i, (short) (buffer.get(i) * mVolume));
            }
 
            // call parent which will pass the audio onto next tool in chain
            super.onAudioSamples(event);
             
        }
         
    }
 
}

Как всегда, мы сначала создаем IMediaReader и используем метод setBufferedImageTypeToGenerate, чтобы генерировать изображения BufferedImage при вызове IMediaListener.onVideoPicture . Это необходимо для того, чтобы наложить наше пользовательское изображение поверх фактических видеоизображений. Затем мы создаем объекты IMediaWriter и медиа-инструмент и настраиваем цепочку инструментов, как описано выше. Давайте внимательнее посмотрим на пользовательские медиа инструменты.

Во-первых, у нас есть класс StaticImageMediaTool. Он расширяет MediaToolAdapter и переопределяет метод onVideoPicture, так как мы хотим манипулировать потоком видео с этим. В конструкторе мы загрузили файл изображения с помощью метода ImageIO.read . Для этой цели используется логотип JavaCodeGeeks (фактически его уменьшенная версия). Затем в реализованном методе onVideoPicture мы получаем базовый BufferedImage , вызывая IVideoPictureEvent.getImage и создаем объект Graphics2D . Затем мы используем метод Graphics.drawImage для наложения статического изображения. Наконец, мы вызываем родительский метод onVideoPicture, который передает видео следующему инструменту в цепочке.

Затем у нас есть VolumeAdjustMediaTool. Он также расширяет MediaToolAdapter , но переопределяет метод onAudioSamples , который вызывается после декодирования или кодирования аудиосэмплов . Там мы получаем необработанные аудиобайты, вызывая IAudioSamplesEvent.getAudioSamples и корректируем его значение, используя соответствующий класс ShortBuffer . Опять же, после нашей пользовательской обработки мы вызываем родительский метод onAudioSamples, который передает звук следующему инструменту в цепочке. Если мы теперь запустим это приложение, мы увидим добавленное изображение поверх оригинального видео, и громкость звука будет значительно уменьшена.
Вот и все. Транскодирование и манипулирование медиа при поддержке Xuggler . Как всегда, вы можете скачать проект Eclipse, созданный для этого урока. Следите за новыми уроками по Xuggler здесь на JavaCodeGeeks ! И не забудьте поделиться!

Статьи по Теме: