1dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu/*
2dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Copyright 2008 CoreMedia AG, Hamburg
3dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu *
4dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Licensed under the Apache License, Version 2.0 (the License);
5dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * you may not use this file except in compliance with the License.
6dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * You may obtain a copy of the License at
7dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu *
8dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu *     http://www.apache.org/licenses/LICENSE-2.0
9dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu *
10dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Unless required by applicable law or agreed to in writing, software
11dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * distributed under the License is distributed on an AS IS BASIS,
12dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * See the License for the specific language governing permissions and
14dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * limitations under the License.
15dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu */
16dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
17dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhupackage com.coremedia.iso;
18dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
19dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport com.googlecode.mp4parser.AbstractContainerBox;
20dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport com.coremedia.iso.boxes.Box;
21dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport com.coremedia.iso.boxes.MovieBox;
22dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport com.googlecode.mp4parser.annotations.DoNotParseDetail;
23dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
24dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport java.io.*;
25dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport java.nio.ByteBuffer;
26dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport java.nio.channels.FileChannel;
27dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport java.nio.channels.ReadableByteChannel;
28dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport java.nio.channels.WritableByteChannel;
29dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
30dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu/**
31dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * The most upper container for ISO Boxes. It is a container box that is a file.
32dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Uses IsoBufferWrapper  to access the underlying file.
33dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu */
34dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu@DoNotParseDetail
35dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhupublic class IsoFile extends AbstractContainerBox implements Closeable {
36dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    protected BoxParser boxParser = new PropertyBoxParserImpl();
37dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    ReadableByteChannel byteChannel;
38dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
39dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public IsoFile() {
40dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        super("");
41dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
42dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
43dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public IsoFile(File f) throws IOException {
44dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        super("");
45dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        this.byteChannel = new FileInputStream(f).getChannel();
46dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        boxParser = createBoxParser();
47dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        parse();
48dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
49dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
50dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public IsoFile(ReadableByteChannel byteChannel) throws IOException {
51dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        super("");
52dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        this.byteChannel = byteChannel;
53dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        boxParser = createBoxParser();
54dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        parse();
55dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
56dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
57dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public IsoFile(ReadableByteChannel byteChannel, BoxParser boxParser) throws IOException {
58dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        super("");
59dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        this.byteChannel = byteChannel;
60dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        this.boxParser = boxParser;
61dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        parse();
62dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
63dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
64dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
65dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
66dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    protected BoxParser createBoxParser() {
67dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        return new PropertyBoxParserImpl();
68dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
69dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
70dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
71dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    @Override
72dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public void _parseDetails(ByteBuffer content) {
73dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        // there are no details to parse we should be just file
74dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
75dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
76dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public void parse(ReadableByteChannel inFC, ByteBuffer header, long contentSize, AbstractBoxParser abstractBoxParser) throws IOException {
77dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        throw new IOException("This method is not meant to be called. Use #parse() directly.");
78dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
79dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
80dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    private void parse() throws IOException {
81dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
82dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        boolean done = false;
83dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        while (!done) {
84dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            try {
85dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                Box box = boxParser.parseBox(byteChannel, this);
86dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                if (box != null) {
87dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                    //  System.err.println(box.getType());
88dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                    boxes.add(box);
89dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                } else {
90dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                    done = true;
91dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                }
92dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            } catch (EOFException e) {
93dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                done = true;
94dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            }
95dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        }
96dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
97dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
98dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    @DoNotParseDetail
99dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public String toString() {
100dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        StringBuilder buffer = new StringBuilder();
101dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        buffer.append("IsoFile[");
102dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        if (boxes == null) {
103dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            buffer.append("unparsed");
104dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        } else {
105dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            for (int i = 0; i < boxes.size(); i++) {
106dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                if (i > 0) {
107dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                    buffer.append(";");
108dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                }
109dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                buffer.append(boxes.get(i).toString());
110dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            }
111dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        }
112dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        buffer.append("]");
113dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        return buffer.toString();
114dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
115dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
116dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    @DoNotParseDetail
117dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public static byte[] fourCCtoBytes(String fourCC) {
118dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        byte[] result = new byte[4];
119dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        if (fourCC != null) {
120dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            for (int i = 0; i < Math.min(4, fourCC.length()); i++) {
121dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                result[i] = (byte) fourCC.charAt(i);
122dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            }
123dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        }
124dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        return result;
125dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
126dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
127dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    @DoNotParseDetail
128dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public static String bytesToFourCC(byte[] type) {
129dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        byte[] result = new byte[]{0, 0, 0, 0};
130dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        if (type != null) {
131dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            System.arraycopy(type, 0, result, 0, Math.min(type.length, 4));
132dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        }
133dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        try {
134dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            return new String(result, "ISO-8859-1");
135dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        } catch (UnsupportedEncodingException e) {
136dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            throw new Error("Required character encoding is missing", e);
137dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        }
138dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
139dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
140dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
141dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    @Override
142dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public long getNumOfBytesToFirstChild() {
143dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        return 0;
144dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
145dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
146dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    @Override
147dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public long getSize() {
148dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        long size = 0;
149dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        for (Box box : boxes) {
150dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            size += box.getSize();
151dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        }
152dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        return size;
153dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
154dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
155dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    @Override
156dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public IsoFile getIsoFile() {
157dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        return this;
158dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
159dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
160dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
161dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    /**
162dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu     * Shortcut to get the MovieBox since it is often needed and present in
163dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu     * nearly all ISO 14496 files (at least if they are derived from MP4 ).
164dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu     *
165dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu     * @return the MovieBox or <code>null</code>
166dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu     */
167dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    @DoNotParseDetail
168dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public MovieBox getMovieBox() {
169dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        for (Box box : boxes) {
170dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            if (box instanceof MovieBox) {
171dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                return (MovieBox) box;
172dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            }
173dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        }
174dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        return null;
175dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
176dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
177dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public void getBox(WritableByteChannel os) throws IOException {
178dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        for (Box box : boxes) {
179dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
180dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            if (os instanceof FileChannel) {
181dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                long startPos = ((FileChannel) os).position();
182dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                box.getBox(os);
183dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                long size = ((FileChannel) os).position() - startPos;
184dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                assert size == box.getSize();
185dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            } else {
186dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu                box.getBox(os);
187dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu            }
188dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
189dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        }
190dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
191dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu
192dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    public void close() throws IOException {
193dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu        this.byteChannel.close();
194dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu    }
195dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu}
196