/* * RED5 Open Source Flash Server - http://code.google.com/p/red5/ * * Copyright 2006-2012 by respective authors (see below). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.red5.io.flv.impl; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.mina.core.buffer.IoBuffer; import org.red5.io.ITag; import org.red5.io.ITagReader; import org.red5.io.ITagWriter; import org.red5.io.IoConstants; import org.red5.io.flv.IFLV; import org.red5.io.flv.meta.IMetaData; import org.red5.io.flv.meta.IMetaService; import org.red5.io.flv.meta.MetaData; import org.red5.io.flv.meta.MetaService; import org.red5.server.api.cache.ICacheStore; import org.red5.server.api.cache.ICacheable; import org.red5.server.cache.NoCacheImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A FLVImpl implements the FLV api * * @author The Red5 Project (red5@osflash.org) * @author Dominick Accattato (daccattato@gmail.com) * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) * @author Paul Gregoire (mondain@gmail.com) */ public class FLV implements IFLV { protected static Logger log = LoggerFactory.getLogger(FLV.class); private static ICacheStore cache; private File file; private boolean generateMetadata; private IMetaService metaService; private IMetaData<?, ?> metaData; /* * 0x08 AUDIO Contains an audio packet similar to a SWF SoundStreamBlock plus codec information * 0x09 VIDEO Contains a video packet similar to a SWF VideoFrame plus codec information * 0x12 META Contains two AMF packets, the name of the event and the data to go with it * * soundType (byte & 0x01) == 0 0: mono, 1: stereo * soundSize (byte & 0x02) == 1 0: 8-bit, 2: 16-bit * soundRate (byte & 0x0C) == 2 0: 5.5kHz, 1: 11kHz, 2: 22kHz, 3: 44kHz * soundFormat (byte & 0xf0) == 4 0: Uncompressed, 1: ADPCM, 2: MP3, 5: Nellymoser 8kHz mono, 6: Nellymoser * * codecID (byte & 0x0f) == 0 2: Sorensen H.263, 3: Screen video, 4: On2 VP6 * frameType (byte & 0xf0) == 4 1: keyframe, 2: inter frame, 3: disposable inter frame * * http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf */ /** * Default constructor, used by Spring so that parameters may be injected. */ public FLV() { } /** * Create FLV from given file source * * @param file File source */ public FLV(File file) { this(file, false); } /** * Create FLV from given file source and with specified metadata generation * option * * @param file File source * @param generateMetadata Metadata generation option */ public FLV(File file, boolean generateMetadata) { this.file = file; this.generateMetadata = generateMetadata; int count = 0; if (!generateMetadata) { try { FLVReader reader = new FLVReader(this.file); ITag tag = null; while (reader.hasMoreTags() && (++count < 5)) { tag = reader.readTag(); if (tag.getDataType() == IoConstants.TYPE_METADATA) { if (metaService == null) { metaService = new MetaService(); } metaData = metaService.readMetaData(tag.getBody()); } } reader.close(); } catch (Exception e) { log.error("An error occured looking for metadata", e); } } } /** * Sets the cache implementation to be used. * * @param cache Cache store */ public void setCache(ICacheStore cache) { FLV.cache = cache; } /** * {@inheritDoc} */ public boolean hasMetaData() { return metaData != null; } /** * {@inheritDoc} */ @SuppressWarnings({ "rawtypes" }) public IMetaData getMetaData() throws FileNotFoundException { metaService.setFile(file); return null; } /** * {@inheritDoc} */ public boolean hasKeyFrameData() { // if (hasMetaData()) { // return !((MetaData) metaData).getKeyframes().isEmpty(); // } return false; } /** * {@inheritDoc} */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void setKeyFrameData(Map keyframedata) { if (!hasMetaData()) { metaData = new MetaData(); } //The map is expected to contain two entries named "times" and "filepositions", //both of which contain a map keyed by index and time or position values. Map<String, Double> times = new HashMap<String, Double>(); Map<String, Double> filepositions = new HashMap<String, Double>(); // if (keyframedata.containsKey("times")) { Map inTimes = (Map) keyframedata.get("times"); for (Object o : inTimes.entrySet()) { Map.Entry<String, Double> entry = (Map.Entry<String, Double>) o; times.put(entry.getKey(), entry.getValue()); } } ((MetaData) metaData).put("times", times); // if (keyframedata.containsKey("filepositions")) { Map inPos = (Map) keyframedata.get("filepositions"); for (Object o : inPos.entrySet()) { Map.Entry<String, Double> entry = (Map.Entry<String, Double>) o; filepositions.put(entry.getKey(), entry.getValue()); } } ((MetaData) metaData).put("filepositions", filepositions); } /** * {@inheritDoc} */ @SuppressWarnings({ "rawtypes" }) public Map getKeyFrameData() { Map keyframes = null; // if (hasMetaData()) { // keyframes = ((MetaData) metaData).getKeyframes(); // } return keyframes; } /** * {@inheritDoc} */ public void refreshHeaders() throws IOException { } /** * {@inheritDoc} */ public void flushHeaders() throws IOException { } /** * {@inheritDoc} */ public ITagReader getReader() throws IOException { FLVReader reader = null; IoBuffer fileData; String fileName = file.getName(); // if no cache is set an NPE will be thrown if (cache == null) { log.info("FLV cache is null, forcing NoCacheImpl instance"); cache = NoCacheImpl.getInstance(); } ICacheable ic = cache.get(fileName); // look in the cache before reading the file from the disk if (null == ic || (null == ic.getByteBuffer())) { if (file.exists()) { log.debug("File size: {}", file.length()); reader = new FLVReader(file, generateMetadata); // get a ref to the mapped byte buffer fileData = reader.getFileData(); // offer the uncached file to the cache if (fileData != null && cache.offer(fileName, fileData)) { log.debug("Item accepted by the cache: {}", fileName); } else { log.debug("Item will not be cached: {}", fileName); } } else { log.info("Creating new file: {}", file); file.createNewFile(); } } else { fileData = IoBuffer.wrap(ic.getBytes()); reader = new FLVReader(fileData, generateMetadata); } return reader; } /** * {@inheritDoc} */ public ITagReader readerFromNearestKeyFrame(int seekPoint) { return null; } /** * {@inheritDoc} */ public ITagWriter getWriter() throws IOException { if (file.exists()) { file.delete(); } file.createNewFile(); ITagWriter writer = new FLVWriter(file, false); return writer; } /** {@inheritDoc} */ public ITagWriter getAppendWriter() throws IOException { // If the file doesn't exist, we can't append to it, so return a writer if (!file.exists()) { log.info("File does not exist, calling writer. This will create a new file."); return getWriter(); } //Fix by Mhodgson: FLVWriter constructor allows for passing of file object ITagWriter writer = new FLVWriter(file, true); return writer; } /** * {@inheritDoc} */ public ITagWriter writerFromNearestKeyFrame(int seekPoint) { return null; } /** {@inheritDoc} */ @SuppressWarnings({ "rawtypes" }) public void setMetaData(IMetaData meta) throws IOException { if (metaService == null) { metaService = new MetaService(file); } //if the file is not checked the write may produce an NPE if (metaService.getFile() == null) { metaService.setFile(file); } metaService.write(meta); metaData = meta; } /** {@inheritDoc} */ public void setMetaService(IMetaService service) { metaService = service; } }